歡迎光臨
每天分享高質量文章

小談 Java 單元測試

點擊上方“芋道原始碼”,選擇“置頂公眾號”

技術文章第一時間送達!

原始碼精品專欄

 


摘要: 原文可閱讀 http://www.iocoder.cn/Fight/A-little-bit-about-Java-unit-testing 「lepdou」歡迎轉載,保留摘要,謝謝!

  • 什麼是UT?

  • UT有什麼價值?

  • Unit Test & Intergration Test

    • Local Integration Test

    • Remote Integration Test

    • Unit Test

  • case細到什麼程度為好?

  • 總結


什麼是UT?

UT(Unit Test)即單元測試

UT有什麼價值?

大部分的開發都不喜歡寫UT,原因無非以下幾點:

  1. 產品經理天天催進度,哪有時間寫UT

  2. UT是測試自己的代碼,自測?那要QA何用?

  3. 自測能測出bug?都是基於自身思維,就像考試做完第一遍,第二遍檢查一樣,基本檢查不出什麼東西

  4. UT維護成本太高,投入產出比太低

  5. 不會寫UT

總之有無數種理由不想寫UT,作為工作不到三年的菜鳥深有體會。之前在點評工作的時候,團隊的“UT”都集中於RPC的服務端。為啥帶雙引號? 因為RPC的服務端沒有頁面可以功能測試,部署到測試環境測試太麻煩,只能寫UT了。在這個場景下我認為叫“驗證”更合適,驗證不等於測試。 驗證往往只寫主邏輯是否通過,且就一個Case,且沒有Assert,有的是System.out。

本人實習的時候做測試的,那時候知道一個測試模型。如下圖:

(圖一)

圖的意思就是越底層做的測試效果越好,越往上則越差。也就是說大部分公司現在做的功能測試其實是效果最差的一種測試方式。 另外,QA界有個現場:大家都知道功能測試沒技術含量,那如何使自己突出呢?答案就是:自動化測試。現實是沒幾個公司能做好自動化測試, 業界做的比較好的百度算一個。那麼為啥自動化測試這麼難做的?在這個模型當中,越往上黑盒越大,自動化測試難度就越大。 這句話反過來就是越往下自動化測試就越好做?沒錯,UT其實是最容易實現且效果最好的自動化測試。 所以在很多公司出現一種現場:QA寫UT。 
原因總結一下就兩點:開發不願意寫UT,QA想自動化測試解放自己。 以上的模型只是理論上說明UT具有巨大的價值,但是真的如此麽?我只想說,只有真正嘗到UT的好處的甜頭才會意識到UT的價值。

Unit Test & Intergration Test

單元測試和集成測試的界線我相信大部分開發也是不清晰的。個人理解單元測試針對於一塊業務邏輯最小的單元,太抽象。物理上可以簡單理解為一個類的方法, 可以是public方法也可以是private方法。一個單元測試不應該包含外部依賴的邏輯,反之就是集成測試了。 問題的核心就在於此。一個service的一個接口實現可能依賴很多第三方:1.本地其它的service 2.dao呼叫 3.rpc呼叫 4.微服務呼叫。如下圖:

圖二

也就是說你的單元測試,真正呼叫了外部依賴那就是集成測試。這其實很常見對不?我們先說這種情況下如何集成測試。

Local Integration Test

本地集成測試也就是說不依賴與其他行程。包括:service依賴其他本地service或者dao的情況。在講述如何集成測試之前,我們先理一下測試模型,測試主要包含三塊內容:1.資料準備 2.執行邏輯 3.輸出驗證。

第一步:資料準備

在本地集成測試里,資料來源基本上來自於dao,dao來自於sql。也就是在執行一個case之前,執行一些sql腳本,資料庫則使用h2這類memory database, 切記不要依賴公司測試環境的db。下圖是使用spring-test框架的一個case,可以在case執行之前準備我們所需要的各種資料, 另外在執行完case之後,執行clean.sql腳本來清理臟資料。這裡也說明一個case的執行環境是完全獨立的,case之間互不干擾,這很重要。

(圖三)

第二步:執行邏輯最簡單,就是呼叫一下我們測試的方法即可

第三步:驗證

集成測試一般是呼叫service,或者dao的接口驗證。

舉個例子:CRUD操作的集成測試

  1. 呼叫C接口

  2. 呼叫R接口,驗證C成功

  3. 呼叫U接口

  4. 呼叫R接口,驗證U成功

  5. 呼叫D接口

  6. 呼叫R接口,驗證D成功

Remote Integration Test

假設我們一個service實現依賴某個RPC Service

第一步:資料準備

跑到別人家的資料庫插幾條資料?或者跟PRC Service的Owner商量好,搭一個測試環境供我們測試?有些公司還真有專門的自動化測試環境,那麼即使有測試環境,那如何實現各種case場景下,第三方Service很配合的傳回資料給我們?想想都蛋疼。

第二步:執行方法

假設我們成功的解決了第一步中的問題,皆大歡喜。現在來看第二步,假設我們的service裡面呼叫了另一個RPC Service創建了很多資料,跑了無數次case,結果….RPC Service對應的資料庫都是我們的臟資料,如何清理?而且他們敢隨便刪資料嗎?想想也蛋疼。

第三步:輸出驗證

假設我們又愉快的解決了第二步中的問題。現在來看第三步,假設我們的方法執行最終輸出是創建了一個訂單,訂單當然是呼叫訂單Service接口了,那麼我們如何驗證訂單是否成功創建了呢?或許可以呼叫訂單Service查詢訂單的接口來驗證。很明顯大多數情況下並沒有這麼完美。想想也蛋疼呀。

通過以上分析,Local Integration Test是可行的,Remote Integration Test基本不可行。

那麼有沒有什麼辦法解決呢?答案就是Mock

  • 第一步:Mock RPC Service 想傳回什麼資料就傳回什麼資料

  • 第二步:還是Mock接口,想呼叫幾次就呼叫幾次

  • 第三步:這一步等到下麵講完單元測試就明白了

Unit Test

上面我們談到Mock可以解決外部依賴的問題,現在有很多Mock的開源框架比如:mockito。那麼問題來了,既然我們可以mock第三方遠程依賴,為何不mock dao、local service呢?沒錯外部依賴全部mock掉,就是單元測試了。因為我們只關心所測試的方法的業務邏輯,也就是真正高內聚的邏輯單元了。如下圖:

(圖四)

好處如下:

  1. 沒有什麼資料是造不出來的,通通傳回Mock的物件

  2. 代碼中的異常處理代碼,也可以通過mock接口,使之丟擲異常

  3. 不產生任何臟資料

  4. 跑case更快了,因為不用啟動整個專案,相當於Main方法

有人會說,都mock了還測試個蛋蛋。

這就是對於單元測試的理解了,單元測試應該只針對於標的方法的業務邏輯測試,dao、其它service應該在它們自身的單元測試去測試。對於依賴的第三方,我們應該信任它們能正確的完成我們所預期的。這句話很難理解對不對?

舉幾個例子

例子一:方法的最後是執行dao的create操作,那麼該如何驗證?

我們應該驗證的內容是:

  1. dao的create方法被呼叫了

  2. 呼叫次數是對的

  3. 呼叫引數也是對的

沒錯,只要這三個驗證通過,那麼這個case執行就是通過的。因為我們相信dao的create操作能正確的完成我們所預期的,只要我們呼叫了正確的次數並且引數都是對的。dao的執行的正確性保證是在該dao的單元測試做的。 在Remote Integration Test裡面第三步驗證道理是一樣的,我們應該驗證RPC接口被呼叫了且次數和引數都是對的,那麼我們的case就算通過了,至於,RPC服務端是否正確執行是它們的事情不是我們所關心的。 Mockito框架的verify接口就是做這件事情的。如果你理解了上述內容,那麼你就開竅了,UT不在變得這麼難寫。

什麼時候用單元測試,什麼時候用集成測試?

在本人的實踐中摸索發現,對於簡單的業務,比如crud型的瘦service,比較適合於集成測試。

以下情況適合於單元測試:

  1. Util類

  2. 含有遠程呼叫的方法

  3. 輸入少,業務邏輯複雜的方法

  4. 需要異常處理的方法

case細到什麼程度為好?

這個問題也是比較經典的,一個方法要是所有的路徑都改寫到,那麼要寫很多的case,說真的累死人。我的建議是兩個原則:

  1. 核心邏輯,容易出錯的邏輯一定要改寫到

  2. 根據自己的時間。 沒必要寫的非常多,畢竟case維護成本很高,業務邏輯一改,case得跟著改。

總結

本人目前在從事於開源專案(Apollo(配置中心))研發,開源專案對代碼質量要求相對來說高一些,UT當然是很重要的一環。剛開始也不會寫UT,當然態度上也不重視UT。老大的代碼UT改寫率很高,抱著對開源負責的態度慢慢接受學習UT,到後來嘗了幾次甜頭後,發現UT真的很實用,價值也很高,但是很遺憾UT被大部分開發所忽略。當然本人對UT的理解、實踐還不夠,仍需繼續實踐樣式。

最後說一句:當開發完功能,跑完UT,你可以放心的上線了的時候,你的UT就成功了。




如果你對 Dubbo 感興趣,歡迎加入我的知識星球一起交流。

知識星球

目前在知識星球(https://t.zsxq.com/2VbiaEu)更新瞭如下 Dubbo 原始碼解析如下:

01. 除錯環境搭建
02. 專案結構一覽
03. 配置 Configuration
04. 核心流程一覽

05. 拓展機制 SPI

06. 執行緒池

07. 服務暴露 Export

08. 服務取用 Refer

09. 註冊中心 Registry

10. 動態編譯 Compile

11. 動態代理 Proxy

12. 服務呼叫 Invoke

13. 呼叫特性 

14. 過濾器 Filter

15. NIO 服務器

16. P2P 服務器

17. HTTP 服務器

18. 序列化 Serialization

19. 集群容錯 Cluster

20. 優雅停機

21. 日誌適配

22. 狀態檢查

23. 監控中心 Monitor

24. 管理中心 Admin

25. 運維命令 QOS

26. 鏈路追蹤 Tracing


一共 60 篇++

原始碼不易↓↓↓

點贊支持老艿艿↓↓

赞(0)

分享創造快樂