作者 | S.L
關於測試
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 )
往期推薦
本文分享自微信公衆號 - 程序猿DD(didispace)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。