最近一直對測試覆蓋率方面的內容比較感興趣,雖然很多項目都早已經用上了Jacoco來實現測試覆蓋率的統計,但是很少看到實際項目中基於覆蓋率統計來指導測試的實踐。這篇文章是我近期基於代碼變更風險(CR)平台 (http://cr.qa.netease.com/)對猛獁大數據系統的后台調度組件Azakban的一個小版本的測試實踐。
個人認為,代碼覆蓋率應該是對一個版本測試情況的一個重要考量。不能說代碼覆蓋率高,就是沒有風險。但是,相反,我覺得如果代碼覆蓋率很低,那就是客觀地存在較大風險,說明我們測試的充分度不夠。CR平台,其實是基於jacoco覆蓋率統計的結果之上,將提測版本與基准版本(通常是上一個線上穩定版本)的代碼庫進行比對,統計出一個版本提測后開發變更代碼的覆蓋情況。另外,借助了其他開源工具(ckjm、javancss等)展示了代碼中類之間的相互調用關系以及復雜度信息等等。
下面是我在猛獁4.8.5版本中,借助CR平台對Azkaban相關需求的測試實踐:
例子一:
1. 看需求和JIRA
開發寫的需求:
“ 補數據優化,增強順序性,優化實例生成數量:原來有的邏輯是Trigger每個一分鍾觸發一次,然后產生一個補數據實例到分發隊列,分發隊列每次分發前,先檢查有沒有正在運行的實例,如果沒有就隨機選擇一個實例分發下去執行。如果有,重新放回隊列。這個邏輯存在的問題是,會產生大量的實例在分發隊列,耗費內存和CPU時間。現在做的優化是:Trigger每次觸發前,先檢查分發隊列以及已經分發下去的實例里邊是否有相關的補數據實例。如果有,則跳過這次觸發。另外可以通過
az.backfill.concurrent.num 指定補數據實例產生的並發度(范圍1~100,缺失默認值 1)。”
看着可能有點暈,我稍微將它簡化一下就是:
a. 補數據調度,原本可能會產生大量並發執行的實例,現在限制並發實例數只能是1。即:前一次實例執行完成后,后一次調度實例才會生成。
b. 提供參數, az.backfill.concurrent.num來控制允許的並發實例數量,該參數的范圍是1-100。
至於JIRA,我這邊就不重復添圖了,大致跟需求內容差不多。
2. 查看Gitlab:
開發提交gitlab代碼時與jira做了關聯,點擊鏈接可以直接跳轉到gitlab:
可以看到該jira影響的類有兩個,分別是:GlobleAttribute.java (只添加了一行常量定義)和 TriggerManager.java
3. 查看CR:
開始測試該功能前,先看一眼CR的覆蓋情況(CR之前執行過自動化測試用例和其他手工測試用例):

可以看到,在表格里沒有出現GlobleAttribute.java(不知是否是因為該類只添加了一行常量定義,改動太小的緣故),而TriggerManager.java類的改動部分覆蓋率為63.6%。點進去再仔細看下新修改的行的覆蓋情況:

可以看到“新修改”的未覆蓋部分(紅色)主要是因為 if (t.getContext().containsKey(GlobleAttribute.AZ_BACKFILL_INSTANCES_NUM))判斷沒有命中,因為該需求引入了新的參數az.backfill.concurrent.num 指定補數據實例產生的並發度 。而該參數我們還並沒有設置過。
此外,下面的未覆蓋部分,
if 判斷的后半段 flow.getScheduleType().equals(ScheduleType.BACKFILL)未命中的原因是因為我們還並沒有執行過補數據的調度。
4. 執行手工測試
對一個作業流設置補數據調度100次,指定補數據實例產生的並發度為5(設置參數az.backfill.concurrent.num 的值為5),驗證補數據調度按照既定場景正確執行。
5. 測試結果
查數據庫顯示,補數據調度按照順序執行, 但是實例個數始終為1。
顯然,該需求的基本功能生效了(從以前的並發實例數不受限,現在實例個數為1),但是並發度參數並沒有生效。
6. 結果分析排查
重新執行覆蓋率統計,再來看一眼CR平台的新的結果:
可以看到,覆蓋率有一定的提升。但是為什么並發參數設置沒有生效呢? 來看一眼服務器上的執行日志,拉到日志的最后,看到有一條頻繁打印的日志:“backfill trigger 3959, .......”:
再對照一下CR平台上變更的代碼:
可以看到,下面這部分“新修改”的代碼應該打印的日志還處於“未覆蓋”的狀態。往上看,發現紅框中圈出的代碼段里打印的日志剛好就是服務器上后台的日志。看一眼判斷條件:
“判斷該作業流是否已經有執行實例正在執行中,且該正在執行中的作業流實例的開始時間距離現在不到10分鍾
” 。
這樣就初步解釋了 目前階段為什么我們沒看到“新修改”日志打印,以及實例沒有生成的直接原因。即: 被之前的判斷條件錯誤攔截了。
等待10分鍾,我們繼續查看數據庫的結果,發現並發的實例數仍為1。 再看一眼CR的覆蓋情況:

可以看到,剛才未覆蓋的這段日志打印,在10分鍾過去后被執行了。為了確認該數據的准確性,再看一眼日志,這次我們直接搜索“Reach az.backfill.instances.num ......”這條日志:

可以看到,日志確實出現了變化。也就是說,服務器執行的代碼進入了判斷: “到達了補數據實例個數的上限 isReachBackfillInstanceNum(t)” 。
然而,我們設置的上限明明是5,為什么卻顯示已經達到上限,而數據庫里的實際並發數還是1呢?
再看一眼,變更代碼中未覆蓋的變更行:

可以發現,在isReachBackfillInstanceNum(t)方法中,剛好找到了我們想要的內容:默認的補數據實例個數backfillInstancesNum為1,當調度t的上下文信息context中包含參數GlobalAttribute.AZ_BACKFILL_INSTANCES_NUM時,就用該對應的值賦值給backfillInstancesNum。且會對該范圍進行1-100的判斷。
而這段代碼正好沒有被執行。
再次進入源碼查看,發現了個驚人的事情:

這個AZ_BACKFILL_INSTANCES_NUM參數的實際key值,應該是az.backfill. instances.num,而開發兄弟在需求和jira中寫的都是“az.backfill. concurrent.num”。
此時,我的心情是復雜的。此處需要省略1000字。
如你所知,后續修改參數key后,重新測試,成功測試通過。通過后的覆蓋率統計情況:

唯一沒有覆蓋的行,是因為設置的並發參數的值是5,在該1-100范圍內,所以沒有進入該判斷。
例子二:
篇幅有限,再來簡單介紹另外個利用CR平台完善測試的例子。
需求:
將執行節點的流實例並發數量的值寫進db,在節點服務啟動時,優先從db中讀取加載進內存。如果db中不包含,再從配置文件讀取。
簡單測試后, 看下CR平台的覆蓋情況:

可以看到,對於基本的正常功能,我們還是覆蓋到了。 但是仍有大部分語句沒有覆蓋,而這部分未覆蓋的內容其實就是查詢數據庫時發生異常情況。
知道未覆蓋的原因,就好辦了,我們只要制造數據庫查詢異常就可以模擬該場景。
測試方式:
執行數據庫語句alter table executors drop column threads_num; 將mysql中executor表的threads_num字段給刪掉。然后重新啟動executor。並確保executor可以正常工作。
再次執行覆蓋率統計,查看CR平台的結果:

可以看到,這下“新修改”的代碼全部覆蓋了。可以妥妥地在jira上標上這功能測試通過。
總結
最近看了一些業界的測試方法,發現基於覆蓋率統計的白盒測試的熱度正在逐漸提升,包括騰訊、去哪兒等一些公司都有基於覆蓋率統計指導測試的不少實踐。包括分析代碼增量覆蓋率,分析每條用例對覆蓋率的貢獻來精簡測試用例集、剔除無效用例,基於執行覆蓋統計將每條用例與代碼模塊相對應等等。所幸,我們也有CR平台,基於CR平台,我們后續可以做的事情應該還有很多。