關(guān)于單測技術(shù)選型,聊聊我的思考
對于單測來說,目前常用的單測框架有:
- JUnit
- Mockito
- Spock
- PowerMock
- JMockit
- TestableMock
其中 JUnit 不支持 Mock,因此基本不會只用 JUnit,而是結(jié)合其他有 Mock 功能的框架一起使用。從知名度及使用率來說,Mockito 和 Spock 使用較多,而 PowerMock、JMockit、TestableMock 使用較少。下面我們將主要對比 Mockito 和 Spock 兩種框架的差異。
Mockito
Mockito 是 Java 單元測試中的 Mock 框架,一般都是與 JUnit 一起使用。Mockito 功能強(qiáng)大,幾乎所有你能想到的功能都支持,并且由于發(fā)布時(shí)間較長,因此使用的人非常多。
- 優(yōu)點(diǎn):功能強(qiáng)大、使用人數(shù)多、資料豐富。
- 缺點(diǎn):代碼不夠簡潔、沒有統(tǒng)一的單測結(jié)構(gòu)、不支持靜態(tài)方法和私有方法 Mock。
更多信息詳見官網(wǎng):https://site.mockito.org/
Spock
Spock 是一個(gè)企業(yè)級的測試規(guī)范框架,可用來測試 Java 和 Groovy 應(yīng)用。Spock 最大的特色是其簡潔美觀的語言規(guī)范。Spock 兼容絕大多數(shù) IDE、編譯工具和 CI 集成服務(wù)器。Spock 框架使用 Groovy 語言編寫,而 Groovy 語言則是 Java 語言的超集,絕大多數(shù) Java 語言語法在 Groovy 中都支持。
- 優(yōu)點(diǎn):單測結(jié)構(gòu)統(tǒng)一、代碼簡潔、異常測試及參數(shù)測試支持更好。
- 缺點(diǎn):學(xué)習(xí)成本略高、不支持靜態(tài)方法和私有方法 Mock。
更多信息詳見官網(wǎng):https://spockframework.org/
Mockito vs Spock
在 Spock vs JUnit 5 - the ultimate feature comparison 中詳細(xì)對比了 Mokito 與 Spock 的差異,他們在發(fā)展情況、學(xué)習(xí)曲線、工具支持等方面的比較如下圖所示。
從上圖可以看到,Mockito 框架在發(fā)展、學(xué)習(xí)曲線、工具支持、從 JUnit4 遷移幾方面比較有優(yōu)勢。而 Spock 框架則在測試結(jié)構(gòu)、異常測試、條件測試等方面比較有優(yōu)勢。因此,選擇哪個(gè)測試框架完全基于實(shí)際情況。例如,如果你目前的情況是:
- Java 是唯一的語言。
- 想要更強(qiáng)的編譯時(shí)錯(cuò)誤檢查。
- 更穩(wěn)定、更主流的實(shí)現(xiàn)方式。
那么選擇 JUnit + Mockito 的方式是更好的選擇。但如果你目前的情況是:
- 希望單測跟簡單易讀
- 更簡潔的參數(shù)測試與異常測試
那么選擇 Spock 會是更好的選擇。
為啥選擇 Spock?
根據(jù)前面的分析,Mockito 的主要優(yōu)勢在于比較穩(wěn)定、主流,缺點(diǎn)在于不夠簡潔易讀。而 Spock 雖然使用人群沒有 Mockito 那么多,但國內(nèi)也有一些大廠在使用 Spock,例如美團(tuán)等(可參考:Spock 單元測試框架介紹以及在美團(tuán)優(yōu)選的實(shí)踐)。
我們重視寫單測,但是又不希望寫單測花費(fèi)太多時(shí)間,畢竟業(yè)務(wù)才是第一位的。因此,我們希望單測代碼盡可能簡潔、可維護(hù)。 基于這個(gè)原因,我們選擇了 Spock 框架作為朝昔后端的單測框架解決方案。而 Spock 不支持 static 方法及 private 方法 Mock 的缺陷,則嘗試通過整合 PowerMock 或 TestableMock 來解決。
可維護(hù)性更強(qiáng)
在極客時(shí)間《程序員的測試課》中,有一節(jié)關(guān)于講了一個(gè)好的自動(dòng)化測試長什么樣?在這里面,作者提到一個(gè)好的單測應(yīng)該由 準(zhǔn)備、執(zhí)行、斷言、清理 4 個(gè)階段組成。
對于 Mockito 而言,它并沒有規(guī)定具體的代碼規(guī)范,因此只能依靠注釋來標(biāo)注哪些代碼是準(zhǔn)備階段的代碼,哪些是執(zhí)行階段的代碼,哪些是斷言階段的代碼,如下代碼所示。
對于 Spock 而言,其通過 given-when-then 的結(jié)構(gòu),強(qiáng)制要求編寫者將不同階段的代碼放到不同的位置,從而增強(qiáng)了可讀性。同樣是用于測試計(jì)算器的加法函數(shù)的單測用例,使用 Spock 框架編寫的單測如下代碼所示。
可以看到,通過 given-when-then 結(jié)構(gòu)的劃分,我們可以更加快速地弄清楚單測的內(nèi)容,從而提高單測的可讀性,使得單測更加容易維護(hù)。
代碼更加簡潔
對于 Mockito 與 Spock 而言,它們之間的一個(gè)很大的差別是:Spock 的代碼更加簡潔。這個(gè)特性可以讓我們編寫比 Mockito 更少的代碼,從而實(shí)現(xiàn)同樣的功能。例如在 Mockito 中,我們 Mock 某個(gè)接口實(shí)現(xiàn)時(shí),通常需要寫一長串的 give(...).return(...)? 代碼。而在進(jìn)行斷言的時(shí)候,也需要寫比較長的 then(xx).should(xx).checkxx() 代碼,如下圖所示。
但在 Spock 中的代碼就相對比較簡潔,如下所示代碼實(shí)現(xiàn)了上述 Mockito 代碼同樣的功能。
可以看到,Spock 沒有 given、willReturn 等關(guān)鍵詞,而是取而用 >> 等符號來實(shí)現(xiàn),這樣代碼更加簡潔,閱讀起來也更加明了。
案例代碼對比:https://www.yuque.com/lugew/spock/wkxhvk