一次單元測試優(yōu)化的過程總結(jié)
前言
淘寶原用戶增長團隊(現(xiàn)用戶運營平臺團隊)是比較早踐行單測增量覆蓋率的團隊,堅持了近兩年下來,我們積累了數(shù)千個test case,在開發(fā)新功能、修改原功能的過程中幫助我們發(fā)現(xiàn)了許多問題,顯著地提升了代碼質(zhì)量、減少線上故障。在這里鄭重地向大家推薦,單測是值得認真做的,開頭是痛苦的,但是積累一段時間后,量變就會帶來質(zhì)變。
言歸正傳,接下來談一談最近在實踐單測過程中遇到的一個問題。在研發(fā)協(xié)同平臺aone(下文簡稱aone)的發(fā)布流水線中,我們針對單元測試設(shè)置了增量代碼覆蓋率85%和test case 100%通過的流程卡點,在每次發(fā)布前,要保證test case完全通過才能提交工單。我們遇到了因并發(fā)導(dǎo)致的test case失敗,調(diào)整并發(fā)度導(dǎo)致的單測時間過長,但又影響研發(fā)效能的問題。最終在并發(fā)度和成功率之間找到了一個平衡點,解決了單測流程降低研發(fā)效率的問題。
單側(cè)流水線配置
在單測流程中呢,我們主要用到了JUnit、JaCoCo和Surefire三套工具,通過aone提供的容器自動化運行單元測試,搜集測試報告。下面簡單介紹一下這三個工具。
? JUnit
java界最大名鼎鼎的單元測試框架,無須多言,會java的應(yīng)該都知道。
? JaCoCo
EclEmma團隊開發(fā)的開源代碼覆蓋率統(tǒng)計工具,也是java業(yè)內(nèi)最主流的代碼覆蓋率統(tǒng)計工具。增量代碼覆蓋率就是通過該工具進行統(tǒng)計的,全量、增量、按類、包統(tǒng)計都支持,非常靈活。
? Maven Surefire Plugin
surefire是maven的一個插件,在maven生命周期的test階段執(zhí)行單元測試用例。運行完成后還會生成測試報告,方便用戶查看單測情況。
我們利用三種工具,加上aone提供的容器和流水線配置能力,完成了自動化單測的流程和發(fā)布卡點校驗。
單元實踐過程
? 兩個階段
- 積累test case時期
在剛剛開始單測時,大家新增的代碼都相對比較獨立,隨著業(yè)務(wù)的發(fā)展、工作職責的調(diào)整,單測會不斷變復(fù)雜,不同的service之間互相交織、單測的維護、運行成本都會增加。我們在這個階段遇到了一個比較棘手的問題。日常開發(fā)過程中,單測都是以類為粒度在本地跑的,都能通過后再去流水線驗證,一旦提交到流水線,就會遇到個別case失敗的問題,一開始排查起來完全沒有思路,test case的失敗可以說是隨機的,任何一個類的任何一個用例都有可能失敗。
經(jīng)過分析和排查,得出結(jié)論是并發(fā)導(dǎo)致的,于是我們限制了并發(fā),做了如下配置,確實解決了這個問題。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<reuseForks>false</reuseForks>
<forkCount>1</forkCount>
</configuration>
</plugin>
大家可以留意一下reuseForks和forkCount參數(shù),這時候我們還沒有深究兩個配置的含義,只是簡單的限制了并發(fā),這也為后續(xù)的故事埋下了伏筆。
- test case達到一定規(guī)模時期
在完成了test case的初始積累以后,新的問題又隨之而來。因為沒有并發(fā),test case又很多,所以每次單測運行時長長達50分鐘。也嚴重影響了大家的研發(fā)效率。在分秒必爭的發(fā)布窗口期,經(jīng)常會出現(xiàn)大家等著單測跑完提交發(fā)布單的情況。
? 問題
看了上述兩個不同階段反映的問題,本質(zhì)上就是成功率和實效性的trade off問題,如何能提高并發(fā)、提升運行速度的同時保障成功率,這就是我們需要解決的最終命題。
? 原理和解決方案
上文提到了reuseForks和forkCount參數(shù),這些都是maven-surefire-plugin提供的配置項,把surefire插件研究清楚了,應(yīng)該就能解決如何兼顧速度和實效性的問題。
- Surefire配置詳解
parallel
jvm內(nèi)并行執(zhí)行
通過parallel參數(shù)開啟,可選為methods,classes,both,suites等
其他參數(shù)
- useUnlimitedThreads,不限制線程數(shù)
- threadCount,線程數(shù)
- perCoreThreadCount,每核(默認true,和threadCount組合使用)
- parallelTestsTimeoutInSeconds,timeout時間
注
- 設(shè)置了parallel后,useUnlimitedThreads或者threadCount必須設(shè)置一個,不然會報錯
- parallel級別還有suitesAndClasses等更復(fù)雜的配置項,本文不多探討
參數(shù)示例如下,代表methods級別并發(fā),10條線程執(zhí)行。
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<configuration>
<parallel>methods</parallel>
<threadCount>10</threadCount>
</configuration>
</plugin>
</plugins>
fork
- 多jvm并行執(zhí)行
- forkCount 最多同時生成的JVM個數(shù),特殊語法是nC,代表n倍的CPU核數(shù),2.5C在4核機器上就是10的意思。
- reuseForks 是否重復(fù)使用fork出的JVM,true代表一個測試類運行完后,進程繼續(xù)處理下一個,false代表一個類運行完了JVM銷毀,重新生成新的JVM
默認配置 forkCount=1/reuseFork=true,forkCount設(shè)置為0會被自動替換為1
parallel和fork
parallel和fork組合后,就可以有更好的并發(fā)效率,也會帶來更大的沖突可能。
- 并發(fā)導(dǎo)致case失敗原因
surefire的文檔原文如下,
簡單說來,就是因為JUnit的實現(xiàn)機制,對于JVM內(nèi)的線程并發(fā),會出現(xiàn)一些race condition或者其他難以復(fù)現(xiàn)的問題;對于forkCount大于1且開啟復(fù)用的情況,因為測試類是在復(fù)用的JVM內(nèi),也會因為相同的原因產(chǎn)生并發(fā)問題導(dǎo)致測試失敗。
- 結(jié)果和建議
在徹底搞清楚surefire的配置原理后,我們回到問題來。經(jīng)過各種排列組合的嘗試,我們得出了比較合適的配置,reuseForks=true/ forkCount=2C,最終效果是每次運行時間在10分鐘左右,出錯概率較低,通過重跑也能解決。
小tip
mvn默認是按模塊串行的,可開啟并行提高整體速度(例:mvn -T 1C clean test),但是在我們的場景下,2000多個test case有1800個都在一個模塊里,所以開啟并行的效果不大。
其實這個問題沒有最優(yōu)組合,只有最合適的組合。在優(yōu)化了這個單測耗時最久的應(yīng)用后,我們又分析了其他幾個應(yīng)用,有的應(yīng)用test case不多,單測運行時長不長,就沒有必要開啟并發(fā),優(yōu)先保證成功率即可;有的應(yīng)用test case直接相互干擾較小,并發(fā)度可以調(diào)整得更高……
總的來說,在弄明白了原理之后,還需要具體情況具體分析,“紙上得來終覺淺,絕知此事要躬行”,大家可以分析一下自己應(yīng)用的情況,結(jié)合surefire的并發(fā)機制進行實踐,相信測過幾次以后就能找到最合適的配置組合。
單元實踐過程
在整個過程中,筆者還留有兩個想法:
- 有沒有辦法通過提高單測代碼質(zhì)量來避免或者降低因為并發(fā)引起的失敗?一些思路是通過suite分組,將可能沖突的類分開跑,這樣的做法可能會極大的提高單測開發(fā)成本,投入產(chǎn)出比不高。
- test case通過率可以不用嚴格卡100%,設(shè)定到99.5%都能顯著的提升效率,因為每次失敗的test case是不固定的,所以偶發(fā)的個別問題不影響整體的回歸。
在實踐卓越工程的過程中,筆者深切的感受到縱觀整個軟件研發(fā)的生命周期,有很多值得研究和切入的點,一些微小的改動,都能有效地提升研發(fā)效能和交付質(zhì)量。在當前的環(huán)境下,業(yè)務(wù)競爭日趨激烈,所謂開源節(jié)流,“開源”難,重心就會偏向“節(jié)流”,降本增效一定會是下一個階段的重點。而且對于技術(shù)人來說,效率一定是永遠的追求。其實提升性能、效率往往不是特別高大上的事情,希望大家能在日常繁重的工作之余,有點時間做些有趣的研究,享受技術(shù)帶來的快樂!