一、在自動化每日構建中運行單元測試和集成測試,如使用持續集成工具自動化構建;多線程
二、基於速度和類型佈局測試:工具
根據運行測試所花費的時間很容易就能區分集成測試和單元測試,把集成和單元測試分開放置,放在不一樣的目錄,指定單元測試和集成測試運行的頻率。佈局
三、確保測試時源代碼管理的一部分,共同放在版本管理器進行管理。單元測試
四、將測試類映射到被測試代碼測試
建立測試類時,應該怎樣組織和放置它們呢?咱們但願能夠找到一個項目的全部相關測試,一個類的全部相關測試,一個方法的全部相關測試。咱們能夠採用如下方式:ui
(1)測試類和被測試類放到同一個項目內;spa
(2)測試類和被測試類儘可能保持相同或類似的包層次;線程
(3)針對同一個被測試方法的多個測試方法命名,能夠採用如userLoginTest_Success,userLoginTest_Fail等。code
五、構建測試API,如使用測試類繼承模式,建立測試工具類等;對象
一、編寫可靠的測試
(1)決定什麼時候刪除或修改測試
單元測試什麼時候會執行失敗?
產品缺陷,沒必要修改測試,只需修復產品缺陷;
測試缺陷,須要修復測試;
產品語義或API變動,使用方式改變了,須要修改測試;
重命名含義不清的測試,重構不可讀的測試。
刪除重複測試。
(2)避免測試中的邏輯
若是單元測試包含了switch、if、else、foreach、for、while等語句就說明你的測試裏包含了不該有的邏輯。
若是須要複雜大型測試,如多線程測試,你應該在標明爲集成測試的包裏編寫這種測試。
以下測試代碼也包含了不該有的邏輯,無心中重複了產品代碼的邏輯user + greeting,
1 public void addString(){ 2 String user = "USER"; 3 String greeting = "GREETING"; 4 String actual = MessageBuilder.Build(user, greeting); 5 6 assertEqual(user + greeting, actual); 7 }
改爲以下代碼就消除了引入邏輯:
1 public void addString(){ 2 String user = "USER"; 3 String greeting = "GREETING"; 4 String actual = MessageBuilder.Build(user, greeting); 5 6 assertEqual("USER GREETING", actual); 7 }
(3)只測試一個關注點
一個測試方法裏保持只有一個斷言,咱們就更容易診斷出了什麼問題。
(4)把單元測試和集成測試分開
單元測試很容易運行,集成測試極可能失敗,若是不夠穩定,開發人員就會跳過全部測試,沒法發揮單元測試的做用。
(5)用代碼審查確保代碼覆蓋率
若是沒有作代碼審查,代碼覆蓋率統計的結論沒有說服力。由於開發人員可能在測試方法裏不寫一個斷言,測試總能經過。
代碼審覈有助於提高團隊的技術水平,還能夠創造出可讀、高質量、可以持續使用多年的代碼,並使你充滿自信。
二、編寫可維護的測試
(1)測試私有或受保護的方法
使方法成爲公共方法;
把方法抽取到新類;
使方法成爲靜態方法。
(2)去除重複代碼
抽取輔助方法去除重複代碼。
使用@Before或者@After去除重複代碼;
(3)已可維護的方法使用@Before
侷限性:
@Before方法只用於須要進行初始化工做時;
@Before方法應該只包含適用於當前測試類中全部測試的代碼,不然這個方法會更難以閱讀和理解。
儘可能不用@Before方法,而封裝輔助初始化方法,每一個測試方法手動調用。這樣增長代碼可讀性。
(4)實施測試隔離
定義:一個測試應該老是能獨立運行,不依賴於任何其餘測試。
測試隔離的臭味道:
強制的測試順序:測試須要特定的順序執行,或者來自其餘測試結果的信息;
隱藏的測試調用:測試調用其餘測試;
共享狀態損壞:測試共享內存裏的狀態,卻沒有回滾狀態;
外部共享狀態損壞:集成測試共享資源,卻沒有回滾資源;
(5)避免對不一樣關注點屢次斷言
1 @Test 2 public void CheckVariousUsmResult(){ 3 assertEqual(3, sum(1001, 1, 2)); 4 assertEqual(3, sum(1, 1001, 2)); 5 assertEqual(3, sum(1, 2, 1001)); 6 }
以上單元測試使用了三個簡單的斷言,進行了三個不一樣的子功能測試,但願能節省一些時間。這樣作法有什麼問題呢?若是斷言失敗,會拋出異常,後續的斷言將得不到執行,即後續的功能得不到測試。但這種狀況下,即使一個斷言失敗了,你仍是會但願知道其餘的斷言結果。
你能夠才起別的方式實現這個測試:
給每一個斷言建立一個單獨的測試;
使用參數化測試(.Net支持,Java目前好像不支持);
把斷言放在一個try-catch塊中。
(6)對一個對象的多個狀態的比較時,有兩種方式:
方法1、多斷言方式
1 @Test 2 public void compare(){ 3 String userName = "zhangf"; 4 String realName = "張飛"; 5 String id = "1001"; 6 User user = new User(id, userName, realName); 7 8 assertEqual(id, user.getId()); 9 assertEqual(userName, user.getUserName()); 10 assertEqual(realName, user.getRealName()); 11 }
方法2、單個斷言方式,toString()比較
1 @Test 2 public void compare(){ 3 String userName = "zhangf"; 4 String realName = "張飛"; 5 String id = "1001"; 6 User user = new User(id, userName, realName); 7 assertEqual("id:"+id+",userName:"+userName+",realName:"+realName, user.toString()); 8 }
第一種方式讓人看起來覺得對多個功能作測試,可讀性差,第二種方式可讀性強。推薦第二種方式。
(7)避免過分指定
過分指定是對被測試單元如何實現其內部行爲進行了假設,而不僅是檢查其最終行爲的正確性。
主要有如下幾種狀況:
測試對一個被測試對象的春內部狀態進行了斷言;
測試使用了多個模擬對象;
測試在須要存根時使用模擬對象;
測試在沒必要要的狀況下指定順序或使用了精確匹配。如對返回的字符串進行精確匹配斷言,而實際只需對字符串的一部分作斷言就能夠了,咱們能夠不適用String.equal(),而使用String.contains()。
三、編寫可讀的單元測試
(1)單元測試命名
測試方法名包括三部分:被測試方法名,測試場景,預期行爲。
如測試用戶登陸,場景是屢次登錄後要求使用驗證碼,預期行爲是密碼錯誤而失敗,可命名爲void userLogin_requirePictureNum_fail(){...}
(2)變量命名
合理的命名變量,能夠確保閱讀測試的人容易理解你要驗證什麼。由於單元測試不只起到測試的做用,仍是做爲API的一種文檔。
很差的命名如魔法數字,assertEqual(-100, result),沒法看出-100是什麼意義,將-100賦值給一個富含表達性命名的變量,如" COULD_NOT_READ_FILE = -100;",而後用變量作equal,則更容易理解斷言的目的。
(3)有意義的斷言
儘可能不要編寫本身的定製斷言信息,若是必須編寫,請命名清楚明白。
(4)斷言和操做分離
反例:
assertEqual(COULD_NOT_READ_FILE, log.GetLineCount("aaa.txt"))
正例:
int result = log.GetLineCount("aaa.txt");
assertEqual(COULD_NOT_READ_FILE, result);
(5)@Before和@After
這兩個方式常常被濫用,以致於方法徹底不可讀。
一種濫用的狀況:在@Before中準備存根和模擬對象,致使閱讀測試的人意識不到測試中使用了模擬對象,也不知道對象的預期值是什麼。
若是由測試方法本身直接設置初始化模擬對象,設置全部的預期值,測試可讀性會更好。
要點:測試要隨着被測試系統一同成長和變化。