姓名:鄒宇翔? ? ? 學號:16019110051
轉(zhuǎn)載自:
http://mp.weixin.qq.com/s/Zs1GnIKyoIJUFUeEQCRijw
【嵌牛導讀】大家都知道程序編好后會經(jīng)過QA的檢測,同時正因為如此大家往往忽略了單元測試的重要性
【嵌牛鼻子】單元測試(UT)
【嵌牛提問】那么應該如何去進行單元測試呢?它對于自己的程序有什么好處呢?
【嵌牛正文】
什么是 UT ?
UT ( Unit Test )即單元測試。
UT 有什么價值?
大部分的開發(fā)都不喜歡寫 UT,原因無非以下幾點:
產(chǎn)品經(jīng)理天天催進度,哪有時間寫 UT。
UT 是測試自己的代碼,自測?那要 QA 何用?
自測能測出 bug ?都是基于自身思維,就像考試做完第一遍,第二遍檢查一樣,基本檢查不出什么東西。
UT 維護成本太高,投入產(chǎn)出比太低。
不會寫 UT。
總之有無數(shù)種理由不想寫 UT,作為工作不到三年的菜鳥深有體會。之前在點評工作的時候,團隊的“UT”都集中于 RPC 的服務端。為啥帶雙引號?
因為 RPC 的服務端沒有頁面可以功能測試,部署到測試環(huán)境測試太麻煩,只能寫 UT 了。在這個場景下我認為叫”驗證”更合適,驗證不等于測試。
驗證往往只寫主邏輯是否通過,且就一個 Case,且沒有 Assert,有的是 System.out。
本人實習的時候做測試的,那時候知道一個測試模型。如下圖:
(圖一)
圖的意思就是越底層做的測試效果越好,越往上則越差。也就是說大部分公司現(xiàn)在做的功能測試其實是效果最差的一種測試方式。
另外, QA 界有個現(xiàn)場:大家都知道功能測試沒技術含量,那如何使自己突出呢?答案就是:自動化測試?,F(xiàn)實是沒幾個公司能做好自動化測試,業(yè)界做的比較好的百度算一個。那么為啥自動化測試這么難做的?在這個模型當中,越往上黑盒越大,自動化測試難度就越大。
這句話反過來就是越往下自動化測試就越好做?沒錯, UT 其實是最容易實現(xiàn)且效果最好的自動化測試。 所以在很多公司出現(xiàn)一種現(xiàn)場: QA 寫 UT。
原因總結(jié)一下就兩點:開發(fā)不愿意寫 UT,QA 想自動化測試解放自己。
以上的模型只是理論上說明 UT 具有巨大的價值,但是真的如此么?我只想說,只有真正嘗到 UT 的好處的甜頭才會意識到 UT 的價值。
Unit Test & Intergration Test
單元測試和集成測試的界線我相信大部分開發(fā)也是不清晰的。個人理解單元測試針對于一塊業(yè)務邏輯最小的單元,太抽象。物理上可以簡單理解為一個類的方法,可以是 public 方法也可以是 private 方法。一個單元測試不應該包含外部依賴的邏輯,反之就是集成測試了。
問題的核心就在于此。一個 service 的一個接口實現(xiàn)可能依賴很多第三方:1.本地其它的 service 2. dao 調(diào)用 3. rpc 調(diào)用 4.微服務調(diào)用。如下圖:
(圖二 )
也就是說你的單元測試,真正調(diào)用了外部依賴那就是集成測試。這其實很常見對不?我們先說這種情況下如何集成測試。
Local Integration? Test
本地集成測試也就是說不依賴與其他進程。包括: service 依賴其他本地 service 或者 dao 的情況。在講述如何集成測試之前,我們先理一下測試模型,測試主要包含三塊內(nèi)容:1.數(shù)據(jù)準備 2.執(zhí)行邏輯 3.輸出驗證。
第一步:數(shù)據(jù)準備
在本地集成測試里,數(shù)據(jù)來源基本上來自于 dao , dao 來自于 sql 。也就是在執(zhí)行一個 case 之前,執(zhí)行一些 sql 腳本,數(shù)據(jù)庫則使用 h2 這類 memory database ,切記不要依賴公司測試環(huán)境的 db 。
下圖是使用 spring - test 框架的一個 case ,可以在 case 執(zhí)行之前準備我們所需要的各種數(shù)據(jù),另外在執(zhí)行完 case 之后,執(zhí)行 clean.sql 腳本來清理臟數(shù)據(jù)。這里也說明一個 case 的執(zhí)行環(huán)境是完全獨立的, case 之間互不干擾,這很重要。
(圖三)
第二步:執(zhí)行邏輯最簡單,就是調(diào)用一下我們測試的方法即可
第三步:驗證
集成測試一般是調(diào)用 service ,或者 dao 的接口驗證。
舉個例子: CRUD 操作的集成測試
調(diào)用 C 接口
調(diào)用 R 接口,驗證 C 成功
調(diào)用 U 接口
調(diào)用 R 接口,驗證 U 成功
調(diào)用 D 接口
調(diào)用 R 接口,驗證 D 成功
Remote Integration? Test
假設我們一個 service 實現(xiàn)依賴某個 RPC Service
第一步:數(shù)據(jù)準備
跑到別人家的數(shù)據(jù)庫插幾條數(shù)據(jù)?或者跟 PRC Service 的 Owner 商量好,搭一個測試環(huán)境供我們測試?有些公司還真有專門的自動化測試環(huán)境,那么即使有測試環(huán)境,那如何實現(xiàn)各種 case 場景下,第三方 Service 很配合的返回數(shù)據(jù)給我們?想想都蛋疼。
第二步:執(zhí)行方法
假設我們成功的解決了第一步中的問題,皆大歡喜?,F(xiàn)在來看第二步,假設我們的 service 里面調(diào)用了另一個 RPC Service 創(chuàng)建了很多數(shù)據(jù),跑了無數(shù)次 case ,結(jié)果?!?RPC Service 對應的數(shù)據(jù)庫都是我們的臟數(shù)據(jù),如何清理?而且他們敢隨便刪數(shù)據(jù)嗎?想想也蛋疼。
第三步:輸出驗證
假設我們又愉快的解決了第二步中的問題?,F(xiàn)在來看第三步,假設我們的方法執(zhí)行最終輸出是創(chuàng)建了一個訂單,訂單當然是調(diào)用訂單 Service 接口了,那么我們?nèi)绾悟炞C訂單是否成功創(chuàng)建了呢?或許可以調(diào)用訂單 Service 查詢訂單的接口來驗證。很明顯大多數(shù)情況下并沒有這么完美。想想也蛋疼呀。
通過以上分析, Local Integration? Test 是可行的, Remote Integration? Test 基本不可行。
那么有沒有什么辦法解決呢?答案就是 Mock
第一步: Mock RPC? Service 想返回什么數(shù)據(jù)就返回什么數(shù)據(jù)
第二步:還是 Mock 接口,想調(diào)用幾次就調(diào)用幾次
第三步:這一步等到下面講完單元測試就明白了
Unit Test
上面我們談到 Mock 可以解決外部依賴的問題,現(xiàn)在有很多 Mock 的開源框架比如: mockito。那么問題來了,既然我們可以 mock 第三方遠程依賴,為何不 mock dao、local service 呢?沒錯外部依賴全部 mock 掉,就是單元測試了。因為我們只關心所測試的方法的業(yè)務邏輯,也就是真正高內(nèi)聚的邏輯單元了。如下圖:
(圖四)
好處如下:
沒有什么數(shù)據(jù)是造不出來的,通通返回 Mock 的對象
代碼中的異常處理代碼,也可以通過 mock 接口,使之拋出異常
不產(chǎn)生任何臟數(shù)據(jù)
跑 case 更快了,因為不用啟動整個項目,相當于 Main 方法
有人會說,都 mock 了還測試個蛋蛋。
這就是對于單元測試的理解了,單元測試應該只針對于目標方法的業(yè)務邏輯測試, dao 、其它 service 應該在它們自身的單元測試去測試。對于依賴的第三方,我們應該信任它們能正確的完成我們所預期的。這句話很難理解對不對?
舉幾個例子
例子一:方法的最后是執(zhí)行 dao 的 create 操作,那么該如何驗證?
我們應該驗證的內(nèi)容是:
dao 的 create 方法被調(diào)用了
調(diào)用次數(shù)是對的
調(diào)用參數(shù)也是對的
沒錯,只要這三個驗證通過,那么這個 case 執(zhí)行就是通過的。因為我們相信 dao 的 create 操作能正確的完成我們所預期的,只要我們調(diào)用了正確的次數(shù)并且參數(shù)都是對的。 dao 的執(zhí)行的正確性保證是在該 dao 的單元測試做的。
在 Remote Integration Test 里面第三步驗證道理是一樣的,我們應該驗證 RPC 接口被調(diào)用了且次數(shù)和參數(shù)都是對的,那么我們的 case 就算通過了,至于, RPC 服務端是否正確執(zhí)行是它們的事情不是我們所關心的。
Mockito 框架的 verify 接口就是做這件事情的。如果你理解了上述內(nèi)容,那么你就開竅了, UT 不在變得這么難寫。
什么時候用單元測試,什么時候用集成測試?
在本人的實踐中摸索發(fā)現(xiàn),對于簡單的業(yè)務,比如 crud 型的瘦 service,比較適合于集成測試。
以下情況適合于單元測試:
Util 類
含有遠程調(diào)用的方法
輸入少,業(yè)務邏輯復雜的方法
需要異常處理的方法
case 細到什么程度為好?
這個問題也是比較經(jīng)典的,一個方法要是所有的路徑都覆蓋到,那么要寫很多的 case,說真的累死人。我的建議是兩個原則:
1. 核心邏輯,容易出錯的邏輯一定要覆蓋到;
2. 根據(jù)自己的時間。 沒必要寫的非常多,畢竟 case 維護成本很高,業(yè)務邏輯一改, case 得跟著改。
總結(jié)
本人目前在從事于開源項目(Apollo(配置中心)? )研發(fā),開源項目對代碼質(zhì)量要求相對來說高一些, UT 當然是很重要的一環(huán)。剛開始也不會寫 UT,當然態(tài)度上也不重視 UT。
老大的代碼 UT 覆蓋率很高,抱著對開源負責的態(tài)度慢慢接受學習 UT ,到后來嘗了幾次甜頭后,發(fā)現(xiàn) UT 真的很實用,價值也很高,但是很遺憾 UT 被大部分開發(fā)所忽略。當然本人對 UT 的理解、實踐還不夠,仍需繼續(xù)實踐模式。
最后說一句:當開發(fā)完功能,跑完 UT,你可以放心的上線了的時候,你的 UT 就成功了。