合格的後端Coder都應該寫好UT和Mock測試

作者 | S.L

來源 | http://r6d.cn/aaTem

關於測試

1 測試都包括哪些

廣義的測試包括 UT、IT、壓力測試、硬件測試等等,這裏重點討論 Unit Test 即單元測試。

2 啥是 UT

單元測試(又稱爲模塊測試, Unit Testing)是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工作。程序單元是應用的最小可測試部件。在過程化編程中,一個單元就是單個程序、函數、過程等;對於面向對象編程,最小單元就是方法,包括基類(超類)、抽象類、或者派生類(子類)中的方法。

簡而言之就是覆蓋你的代碼的一些測試用例,不依賴於任何第三方的服務依賴,如 HTTP 接口、數據庫連接等,只測試功能不依賴於環境,在任何時候人和機器上都可以 Pass。

3 爲什麼要寫 UT

讓你的代碼質量更可靠&讓你對代碼結構更加敏感&迫使你寫更優質的代碼&…

4 爲什麼不寫 UT

!${爲什麼要寫 UT}

5 什麼在阻止你寫 UT

  • 代碼本身的原因

    • 如果代碼複雜度較高還缺少必要的抽象和拆分,就會讓人對寫 UT 望而生畏。
  • 編碼工作量的原因

    • 無論是用什麼樣的單元測試框架,最後寫出來的單元測試代碼量也比業務代碼只多不少,在不作弊的前提下要保證相關的測試覆蓋率,大概要三倍源碼左右的工作量。
  • 難以維護的原因

更多的代碼量,加上單測代碼並不像業務代碼那樣直觀,還有對單測代碼可讀性不重視的壞習慣,導致最終呈現出來的單測代碼難以閱讀,要維護更是難上加難。

6 合格的 UT 什麼樣

至少要滿足:

  • 測試的是一個代碼單元內部的邏輯,而不是各模塊之間的交互。
  • 無依賴,不需要實際運行環境就可以測試代碼。
  • 運行效率高,可以隨時執行。

Java 如何寫 UT

Java 開發一般都是用 JUnit 或 TestNG,我們大多人還是使用 JUnit4。本文不討論語法,只介紹一般性的使用規範。

7 命名

可以參考 7 Popular Unit Test Naming ( https://dzone.com/articles/7-popular-unit-test-naming )

8 Assertion

任何一個 UT 中需要至少包含一個 assert,用 System.out.println()來驗證結果不符合 UT 的規範,一般都是驗證方法的返回結果,如 assertEquals(200, statusCode)而不是 System.out.println(200==statusCode)。

Assertion 只能保證走過的分支的結果是否正確,無法保證一定是走過了某些分支。

9 爲啥要 Mock

不用 Mock 我們自己也能實現測試(如匿名類),只不過對代碼的要求非常高

10 Mock 框架

一些常用的 mock 庫包括 Mockito、JMockIt、EasyMock、PowerMock…沒有優劣沒有好壞,只有合適與否。

比如我個人比較喜歡 Mockito:

  • 第一它相對於其他幾個老牌庫來說比較新並且更新活躍,在 github 中引用的也最多
  • 第二它的 fluent API 風格的代碼可讀性很高跟 JDK8 的 Stream 風格很像
  • 第三它抽象出測試中的經典概念,如 when().thenReturn()、doThrow().when()、verify()、times()、never()以及各種註解很容易理解

11 什麼樣的方法需要 mock

  • 任何被非本類的功能均需要 mock,如數據庫訪問、RPC 接口、外部引入的 jar 包等
  • 環境變量、系統屬性和方法
  • 測試只測試當前類當前方法的功能,依賴方的功能由依賴方的 UT 來保證正確性,本層不負責驗證
  • mock 本質上是一個 proxy,在需要提供功能的時候由開發者提供“僞實現”

12 什麼樣的方法不需要 mock

  • 本類的需要測試的方法依賴的同類方法,該方法的正確性由該方法自身的 UT 來保證
  • 靜態方法,靜態方法由自身的 UT 來保證功能的正確性
  • protected 方法是可以測試的,只要測試代碼類和要測試的類在同一個 package 下面就可以
  • private 方法(有異議),我的看法是私有方法如果邏輯很多,應該重構出來提供 public 方法或者新的 Class 進行重構;如果邏輯不多仍然保證不了無 bug ,可以使用反射來測試。其實 private 方法的測試是需要通過對 public 方法的測試來完成的,因爲 private 方法總是會被 public 方法調用的。還有一種測試方法就是放寬訪問限制,private 方法改爲 protected,並且用 guava 的 @VisibleForTesting 註解標註放寬權限的方法。

13 如何設計適合測試的接口

  • 1.Dependency Injection

如果把一種依賴寫死在方法裏肯定不利於測試,如果該依賴是一種強引用第三方服務的 sdk 你就痛苦了,如配置類初始化時需要連接 zk 且無法注入

  • 2.Abstraction

包括類的抽象、方法的提取,代碼越精簡,測試越方便、越快速、越容易暴露問題

  • 3.開閉原則

面向擴展開發,面向修改閉合,不對老代碼入侵,避免 UT 重複修改

  • 4.慎重聲明 static 方法

最好的 static 方法是完全不依賴任何第三方服務自己可以實現業務邏輯的代碼,如果依賴第三方,使用 reference 傳入而不是寫死在 class 或 method 裏

  • 5.測試類而不是實現

單元測試測試的對象是類,測試類的功能在各種情況下是否符合預期,而不是測試實現。所以我們只需要測試能夠跟其他類交互的 public 方法就可以了。這樣的一個好處就是,如果哪天需要重構代碼的實現,或者換一個算法實現某些方法,但功能不變的情況下,UT 是可以複用的。如果針對實現來測試,如果哪天要重構代碼實現,那 UT 就會 fail 掉。

  • ……待續……

測試覆蓋率

一個仁者見仁智者見智的問題,不做深入討論了。

個人建議工具方法(保證正確性以及邊界條件不出錯)、核心流程(複雜的條件判斷尤其需要 UT 保證)需要重點覆蓋,底層接口如 DAO、簡單的 Service 封裝可以不用寫。

IDEA Plugin

推薦一個 JUnitGeneratorV2.0,可以通過 Command+N 來生成 Test 類或者直接在類名上使用 alt+enter 來生成。

總結

沒有測不了的代碼,只有測試不夠方便的代碼,大多源於設計不夠合理。

寫單元測試的難易程度跟代碼的質量關係最大,並且是決定性的。項目裏無論用了哪個測試框架都不能解決代碼本身難以測試的問題,所以如果你遇到的是“我的代碼裏依賴的東西太多了所以寫不出來單測”這樣的問題的話,需要去看的是如何設計和重構代碼,而不是這篇文章。

Reference

  • https://dzone.com/articles/7-popular-unit-test-naming ( https://dzone.com/articles/7-popular-unit-test-naming )
  • https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2 ( https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2 )

點擊下方卡片關注我,訂閱更多精彩內容


往期推薦

“智能”坐墊記錄離座時間,是高科技福利還是又一個員工壓榨機器?

Java延遲加載的最佳實踐應用示例!

新年新氣象,該換一波壁紙了!

不容錯過的灰度發佈系統架構設計

還在封裝各種 Util 工具類?這個神級框架幫你解決所有問題!

阿里開源臺柱 Ant Design 源碼倉庫被刪了...




本文分享自微信公衆號 - 程序猿DD(didispace)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章