單元測試er——為什么真的真的要寫單元測試


優點

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

方法更健壯

更明確方法的職責

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

明確服務職責邊界

最好是單一的職責(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。

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

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

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM