為什麼真的真的要寫單元測試

作者: IT庄君祥

原文地址:imooc.com/article/detai

優點

為什麼很多技術或者知識要說優點?因為有些道理看著很簡單,大家表面上都覺得對,但是做的時候又不去做或者做不到。其中有一個很重要原因是骨子裡或者潛意識並沒有真實覺得這是對的,一旦想去做的時候同時會冒出更多不去做的理由。

方法更健壯

更明確方法的職責

很多小夥伴在編寫方法或者程序的時候,先簡單寫一下「大體」的邏輯。好一些的,在寫完後,會根據不同"情況"驗證一下,如果有錯再繼續修改。但是往往更多的情況下,自己也不知道這個方法對外是一種什麼形態,需要滿足多少種情況,在異常的情況下提供的是什麼表現。所以最終需要使用者(有可能是服務調用者,測試者或者真正的用戶)來糾正問題,然後再去修訂。

這樣一來,整個編寫方法的周期其實更長,資源的損耗更大。

明確服務職責邊界

最好是單一的職責(web層或者流程聚合的介面除外)。

現在是做一個判空的工具。首先要分析的是這個判空的服務範圍和職責。一個集合判空、一個字元串判空,跟一個同時支持,包裝類,字元串(包括Char等),集合,數組,字典,對象等的判空。這就是兩個完全不同的職責。不同的職責最終的case是不同的。

明確正常case

一般是根據第一步的服務範圍和職責來提供的,這樣是黑盒,和使用者的視角是一樣的(推薦)。也有喜歡通過白盒列case的,通過if等拐點來確定case(不是很推薦)。最終要保證對的肯定是對的,而且要和預期結果一樣。

明確異常的case

特別重要的是需求明確的異常,比如說,需要去支付,但是你的錢是非法的。還有抽象域的一些問題要考慮:比如說:冥等,批量操作時的原子性,依賴服務異常等。最終要保證錯的一定要錯。

明確case輸入

明確每一個case輸入應該是什麼,只關注和這個case相關的,這樣每個都是具義的。如果一個case有太多的輸入和case無關,最好是考慮對依賴的結點進行mock。

明確case輸出

明確每一個case輸出是什麼。這樣可以進行斷點和結果預期。然後執行時,就能反向知道這個方法提供的服務是否正確。如果不正確的話,需要修改方法。

大膽重構

只有有case了,才能使用自動化的驗證。否則有可能只是改了一個很小的地方,但是會引起其他case的錯誤,改一個小地點就得手動的把所有case測試一下。而且最害怕的是歷史方法,因為沒有人能說清楚到底有多少種case。

重構時錯誤常見的場景:

  • 一個判斷條件或者設計的鏈路,想的是對的,但是寫的時候出錯,導致正常業務都出錯了
  • 誤刪或者重構時遺漏代碼,導致部分業務錯誤。

讓編寫的方法更獨立

一旦耦合度太高,在造輸入數據的時候就會特別困難。這樣也反向的能促進我們在寫代碼的時候儘可能的不依賴,至少不深度或者嵌套依賴。

比如:以前是寫個a方法,要知道b方法使用c對象的d屬性。這樣造輸入的時候就特別難受。所以就會促進我們變成寫個a方法,最多使用和關心b方法。其他是b方法的職責,讓b方法自己去測試。

這樣也能讓每個方法更原子和內聚。

隔離依賴

無感依賴細則

不用關注依賴的細則,特別是不用跨層或者跨服務去關注細節。從樹狀結構關注點變為平級關注點。從關注細則到關注服務。

並行開發

以前的方式是,相互耦合依賴,上游沒做完,下游沒數據,沒辦法或者很難並行開發。但是使用隔離後,就可以基於介面的服務職責來mock預期的行為,所以互相就不會依賴,可以並行去開發。

結果可預見

比較頭疼的是,要根據不同的業務case,造各種場景,有的場景還要開關或者編數據等特殊方式才可以。但是使用隔離mock後,想要有什麼預期結果是非常穩定的,也是很簡單自然的。

比如:有N個集合中,調用指定的服務後,如果有部分失敗,部分成功。這個case用mock是非常好造的。

解決重複問題

當前,在編寫單元測試的時候也會有很多工作量,所以可以通過單元測試框架來解決重複的問題。

  • mock簡潔化和自動化。通過註解和ioc基本很容易做到。
  • 設置參數很頭疼,還有很多魔鬼數字,有的時候還得硬著頭皮造一些無厘頭的數據。

哪些需要寫

單元測試不是越多越好,而是越有效越好!進一步解讀就是哪些代碼需要有單元測試覆蓋:(引用Kent Beck)

  • 邏輯複雜的
  • 容易出錯的
  • 不易理解的,即使是自己過段時間也會遺忘的,看不懂自己的代碼,單元測試代碼有助於理解代碼的功能和需求
  • 公共代碼。比如自定義的所有http請求都會經過的攔截器;工具類等。
  • 核心業務代碼。一個產品里最核心最有業務價值的代碼應該要有較高的單元測試覆蓋率。

怎麼寫

  • 根據case準備數據,mock
  • 觸發驗證場景
  • 期待的結果是什麼

何時寫

寫單元測試的時機不外乎三種情況:

  • 在具體實現代碼之前,這是測試驅動開發(TDD)所提倡的;
  • 與具體實現代碼同步進行。先寫少量功能代碼,緊接著寫單元測試(重複這兩個過程,直到完成功能代碼開發)。其實這種方案跟第一種已經很接近,基本上功能代碼開發完,單元測試也差不多完成了。
  • 編寫完功能代碼再寫單元測試。我的實踐經驗告訴我,事後編寫的單元測試「粒度」都比較粗。對同樣的功能代碼,採取前兩種方案的結果可能是用10個「小」的單測來覆蓋,每個單測比較簡單易懂,可讀性可維護性都比較好(重構時單測的改動不大);而第三種方案寫的單測,往往是用1個「大」的單測來覆蓋,這個單測邏輯就比較複雜,因為它要測的東西很多,可讀性可維護性就比較差

我個人推薦的是,先大體明確方法的職責和邊界,然後把突出的case大體設計出來。然後和具體實現代碼同步。一來可以補充case,只有對需求有一定的理解後才能知道什麼是代碼的正確性,才能寫出有效的單元測試來驗證正確性,而能寫出一些功能代碼則說明對需求有一定理解了。二來可以使用重構的思維去解決思考兩次而且還互相打架的問題。

陷阱

多驗證點

多驗證點的case,一旦業務稍微改變一點點,很容易造能case的通過不了,也說明了方法的職責不是很原子。有可能可以進一步拆分。

過度依賴上下文

說明方法不夠健壯,職責不清楚。如果一旦上下文變更,就會導致case的失敗。介時就分不清楚是上下文數據的問題,還是自己服務的問題。

還需要做的事

工具類庫

雖然,單元測試框架做了很多重複的事,但是還有很多重複的事,其實都是可以封裝成工具類的。

比如:一個方法有很多參數,然後每個參數都都可以賦默認值,那就得手寫半天。像這種抽象上一致的都可以封裝成工具類

規範

在不同的單元測試之間,其實有很多重複的思考和溝通。

比如:單元測試的方法名怎麼命名更好些?一個方法放一個case還是多個case。什麼樣的異常需要驗證case。

有了規範或者規約後。重複的內容可以通過代碼片段,文件模板等方式半自動化的生成,甚至可以通過代碼生成器等小工具,默認把一些手工的操作怎麼自動生成。而且規範後,大家閱讀和維護單元測試的成本就會降低。

理想狀態的單元測試,應該是只驗證正確的業務點,和異常的業務點,以及一些從系統和抽象問題領域角度的異常業務點。其他的要麼交給工具,要麼交給規範。

參考資料

  • 為什麼要寫單元測試,何時寫,寫多細
  • mockito官方文檔

本文原創發佈於慕課網 ,轉載請註明出處,謝謝合作


推薦閱讀:

CSS「隱藏」元素的幾種方法的對比

C語言新人常見問題與錯誤

移動開發者最愛的9個優秀Android代碼編輯器

如何做出良好用戶體驗的動效設計

大二女生web開發成長之路——講述我從軟妹子到女漢子的進階過程


推薦閱讀:

SpringBoot使用logback實現日誌按天滾動

TAG:單元測試 | 編程技巧 | 工作經驗 |