初级开发人员在编写单元测试时常犯的错误
自從我編寫第一個單元測試以來已經有10年了。 從那時起,我不記得我已經編寫了成千上萬的單元測試。 老實說,我在源代碼和測試代碼之間沒有任何區別。 對我來說是同一回事。 測試代碼是源代碼的一部分。 在過去的3-4年中,我與多個開發團隊合作,并且有機會查看了大量的測試代碼。 在這篇文章中,我總結了經驗不足的開發人員在編寫單元測試時通常會犯的最常見錯誤。
讓我們看一下以下簡單的類示例,該類收集注冊數據,對其進行驗證并執行用戶注冊。 顯然,該方法非常簡單,其目的是演示單元測試的常見錯誤,而不是提供功能齊全的注冊示例:
public class RegistrationForm {private String name,email,pwd,pwdVerification;// Setters - Getters are ommitted public boolean register(){validate();return doRegister();}private void validate () {check(name, "email");check(email, "email");check(pwd, "email");check(pwdVerification, "email");if (!email.contains("@")) {throw new ValidationException(name + " cannot be empty.");} if ( !pwd.equals(pwdVerification))throw new ValidationException("Passwords do not match.");}private void check(String value, String name) throws ValidationException {if ( value == null) {throw new ValidationException(name + " cannot be empty.");}if (value.length() == 0) {throw new ValidationException(name + " is too short.");}}private boolean doRegister() {//Do something with the persistent contextreturn true;}這是注冊方法的相應單元測試,有意顯示單元測試中最常見的錯誤。 實際上,我已經看過很多次非常相似的測試代碼,所以這不是我所說的科幻小說:
@Testpublic void test_register(){RegistrationForm form = new RegistrationForm();form.setEmail("Al.Pacino@example.com");form.setName("Al Pacino");form.setPwd("GodFather");form.setPwdVerification("GodFather");assertNotNull(form.getEmail());assertNotNull(form.getName());assertNotNull(form.getPwd());assertNotNull(form.getPwdVerification());form.register();}
現在,此測試顯然將通過,開發人員將看到綠燈,所以豎起大拇指! 讓我們轉到下一個方法。 但是,此測試代碼有幾個重要問題。
在我的拙見中,第一個是單元測試的最大誤用是測試代碼沒有充分測試寄存器方法。 實際上,它僅測試許多可能路徑中的一個。 我們確定該方法將正確處理空參數嗎? 如果電子郵件中不包含@字符或密碼不匹配,該方法將如何工作? 開發人員傾向于只為成功的路徑編寫單元測試,而我的經驗表明,代碼中發現的大多數錯誤都與成功的路徑無關。 一個非常好的規則要記住的是,對于每一個方法,你需要N個測試,其中N等于在圈復雜度將所有私有方法調用的圈復雜度的方法。
接下來是測試方法的名稱。 為此,我部分歸咎于所有這些現代IDE,它們自動為測試方法(如示例中的方法)生成愚蠢的名稱。 測試方法的命名應向讀者解釋將要測試的內容和條件 。 換句話說,它應該描述正在測試的路徑。 在我們的情況下,更好的名稱可以是: should_register_when_all_registration_data_are_valid。 在本文中,您可以找到幾種命名單元測試的方法,但是對我來說,“應該”模式最接近人類語言,并且在閱讀測試代碼時更容易理解。
現在,讓我們看一下代碼的內容。 有幾個斷言,這違反了每個測試方法應斷言一件事的規則 。 此聲明四(4)個RegistrationForm屬性的狀態。 這使測試更難以維護和閱讀(哦,是的,測試代碼應該像源代碼一樣可維護和可讀。請記住,對我而言它們之間沒有區別),并且很難理解測試的哪一部分失敗。
此測試代碼還聲明了setter / getter。 這真的有必要嗎? 為了回答這個問題,我將引用羅伊·奧什羅夫(Roy Osherove)的名言:“ 單元測試的藝術 ”
屬性(Java中的獲取器/設置器)是很好的示例代碼,通常不包含任何邏輯,并且不需要測試。 但是要當心:在屬性中添加任何檢查后,您將要確保邏輯已經過測試。
在我們的案例中,設置器/獲取器中沒有業務邏輯,因此這些斷言完全沒有用。 此外,他們錯了,因為他們甚至沒有測試安裝員的正確性。 想象一下,一個邪惡的開發人員將getEmail方法的代碼更改為始終返回常量String而不是email屬性值。 該測試仍將通過,因為它斷言setter不為null,并且未斷言期望值。 因此,這可能是您要記住的一條規則。 斷言方法的返回值時,請始終嘗試盡可能具體 。 換句話說,除非您不關心實際的返回值,否則請盡量避免使用assertIsNull,assertIsNotNull。
我們正在查看的測試代碼的最后但并非最不重要的問題是,從未斷言正在測試的實際方法( 寄存器 )。 它在測試方法內部被調用,但是我們從不評估其結果。 這種反模式的變化甚至更糟。 在測試用例中甚至不會調用被測方法。 因此,請記住, 您不僅應調用被測方法,而且還應始終聲明預期結果,即使它只是一個布爾值 。 有人可能會問:“無效方法是什么?”。 好的問題,但這是另一次討論–可能是另一篇文章,但是為您提供一些提示,測試void方法可能會掩蓋不好的設計,或者應該使用驗證方法調用的框架(例如Mockito.Verify )來完成
作為獎勵,您應該記住這是一條最終規則。 想象一下, doRegister實際上已實現,并且對外部數據庫做了一些實際的工作。 如果某個本地環境中未安裝數據庫的開發人員嘗試運行測試,將會發生什么情況。 正確! 一切都會失敗。 確保測試即使從僅可訪問代碼和JDK的最笨拙的終端運行,也將具有相同的行為 。 沒有網絡,沒有服務,沒有數據庫,沒有文件系統。 沒有!
翻譯自: https://www.javacodegeeks.com/2014/09/common-mistakes-junior-developers-do-when-writing-unit-tests.html
總結
以上是生活随笔為你收集整理的初级开发人员在编写单元测试时常犯的错误的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JSF的工作方式和调试方式–可以使用po
- 下一篇: 飞智ios怎么设置(ios飞智开启飞鼠功