《單元測試的藝術》讀書筆記----測試代碼的最佳實踐

  • 測試層次和組織

        一、在自動化每日構建中運行單元測試和集成測試,如使用持續集成工具自動化構建;多線程

        二、基於速度和類型佈局測試:工具

        根據運行測試所花費的時間很容易就能區分集成測試和單元測試,把集成和單元測試分開放置,放在不一樣的目錄,指定單元測試和集成測試運行的頻率。佈局

        三、確保測試時源代碼管理的一部分,共同放在版本管理器進行管理。單元測試

        四、將測試類映射到被測試代碼測試

        建立測試類時,應該怎樣組織和放置它們呢?咱們但願能夠找到一個項目的全部相關測試,一個類的全部相關測試,一個方法的全部相關測試。咱們能夠採用如下方式: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中準備存根和模擬對象,致使閱讀測試的人意識不到測試中使用了模擬對象,也不知道對象的預期值是什麼。

        若是由測試方法本身直接設置初始化模擬對象,設置全部的預期值,測試可讀性會更好。

 

        要點:測試要隨着被測試系統一同成長和變化。

相關文章
相關標籤/搜索