起點客戶端精準化測試的演進之路
點擊上方藍字關注我,知識會給你力量
精準化測試,實際上就是對「業(yè)務」——「測試用例」——「代碼」進行關聯(lián)建模并追蹤他們的變化的一種測試方法。
在敏捷迭代的團隊里,精準化測試是讓團隊跑的更快、更穩(wěn)的一個重要工具。起點客戶端團隊基于當前的迭代場景,結合自身的一些開發(fā)特定,定制了一套完整的精準化測試體系,對業(yè)務測試的效率和App的穩(wěn)定性,提供了強有力的工具保障。
背景
在敏捷測試過程中,經(jīng)常會遇到這樣的問題:
-
我自測過了,你簡單測下就好了。
-
代碼重構了,我也不知道影響什么業(yè)務……
-
我就升級了SDK,不知道有什么影響……
-
代碼改動挺多的,要么全測一遍吧!
-
我就改了一行代碼,你要測幾天?
這就是當前敏捷開發(fā)模式下,測試和開發(fā)的矛盾會越來越多的原因。快速的迭代,會極大的擴大測試的回歸成本。
所以,敏捷開發(fā)下測試的最終選擇,一定只有兩條路:
-
自動化測試,降低人工成本 -
縮小回歸范圍,提高測試效率
否則大量的回歸測試內(nèi)容,會給測試團隊增加數(shù)倍的工作量。
而它們的優(yōu)先級,一定是優(yōu)先「縮小回歸范圍」,自動化測試只是工具,縮小回歸范圍,才能給團隊帶來效率的提升,這個方向就是通過「代碼分析」,來找到縮減無關的測試內(nèi)容。
例如一個線上問題,對指定機型的設備有bug,開發(fā)進行修復后,對指定機型進行了if else判斷,測試用例可以縮減到當前判斷分別為true和false的場景。
敏捷開發(fā)模式下,唯一不變的東西就是「變化」,測試的過程,就是從變化中找到核心的影響因素,從中分析出需要測什么,不需要測什么。
那么在敏捷迭代的這樣一個環(huán)境下,開發(fā)者和測試怎么來保證,我「提交的代碼」、「測過的Case」在任何時候都是正確的呢?
當你無法量化的時候,你就在用你的人品和信譽做擔保,而開發(fā)團隊對你的信任也是基于你的信譽??墒窃谀氵€沒有建立你的信譽的時候,你就必須拿出量化的東西來贏得信任。
?精準化測試,需要測試從提交的代碼中找到具體的業(yè)務修改點,這對測試的要求很高,他需要從開發(fā)提交的代碼中了解具體修改的業(yè)務邏輯點,而開發(fā)的一個commit,有時候并不是很純粹,經(jīng)常會夾帶一些「私貨」,這也是引起測試未覆蓋的一個重要原因。
?
所以,我們搭建精準化測試平臺的意義,主要在于下面三點:
-
驗證開發(fā)所有的代碼修改,是否被測試用例完全覆蓋 -
避免開發(fā)提交了與Commit信息無關的代碼,導致測試未驗證的代碼出現(xiàn)問題 -
從代碼角度驗證測試用例的完整性
精準化測試平臺的適用場景,主要是下面兩個:
-
功能提測階段:驗證測試用例是否覆蓋修改代碼,避免測試用例遺漏 -
回歸測試階段:驗證新合入代碼是否有未覆蓋的代碼(如果有自動化測試,可以進行回歸驗證)
但是,精準化測試并不是測試質量保證體系的銀彈,對于精準化的覆蓋率測試,需要先明確一個前提公理,那就是Coverage覆蓋到的代碼,就算是testcase執(zhí)行完成了。因為覆蓋率測試只能保障代碼被執(zhí)行,而執(zhí)行邏輯是否正確,并不在保證之內(nèi)。
因此,盲目的追求代碼覆蓋率是沒有意義的,即使已經(jīng)達到了 100%的代碼覆蓋率,軟件的質量也不可能做到萬無一失,因為代碼覆蓋率的計算只是基于執(zhí)行代碼的,并不能發(fā)現(xiàn)那些「未考慮某些輸入」、「未判斷的邏輯異常」以及「未處理某些情況」形成的缺陷。
設計思路
針對上面的這些問題,在開發(fā)的角度,我們希望能對測試的效率進行提升。
技術選型與方案設計
在服務端開發(fā)中,通常使用「單測+覆蓋率」的方式來保證代碼的執(zhí)行覆蓋程度,所以,這里借助代碼覆蓋率,來作為關聯(lián)代碼和用例的橋梁。
在移動端,代碼覆蓋率通常使用JaCoCo,即 Java Code Coverage來實現(xiàn)。
JaCoco作為一個Java平臺下的老牌覆蓋率工具,在開源平臺上已經(jīng)經(jīng)過了多年的驗證,這也是我們選擇它作為技術選型的一個重要依據(jù),它的原理其實非常簡單,就是在代碼中插入大量的探針,探針執(zhí)行過的語句塊即為執(zhí)行過的代碼塊,通過分析探針的執(zhí)行情況,就能算出相應的覆蓋率,下面這張圖就是一個反編譯的探針代碼。
JaCoco默認場景下是不支持增量插入探針的,但在實際開發(fā)過程中,一般不太會對全量代碼做檢測,所以,我們需要改造JaCoco,為精準化測試平臺提供增量探針功能。同時,還需要接入CI系統(tǒng),與現(xiàn)有軟件開發(fā)流程對接,完成「開發(fā)-測試-回歸」的閉環(huán)。
大部分公司的CI系統(tǒng)都會采用Jenkins來搭建,起點在接入騰訊的藍盾之前,也是采用的Jenkins,所以,整體的系統(tǒng)流程圖如下所示。
由于Jenkins Job的特殊性,當我們把增量覆蓋率插件部署到CI后,會有很多流程上無法打通的問題,例如,覆蓋率插樁包編譯好后,需要測試手動測試,再將數(shù)據(jù)統(tǒng)計文件上傳到Jenkins,然后再觸發(fā)新的Job來生成報告,但這一步,在Jenkins上又沒辦法拿到對應編譯包的源碼和class文件,這與本地的流程有很大不同,同時,Jenkins拉取代碼需要使用git commitid,這些內(nèi)容也很難對齊,所以,我們在CI上部署,需要對整個流程做一些改動。
-
創(chuàng)建一個新的Jenkins Job,負責編譯插樁包,在這個Job中,需要執(zhí)行者輸入:分支名、BaseCommitID和EndCommitID,這兩個CommitID,就對應git diff中的對比id,Jenkins根據(jù)指定的CommitID和分支,拉取代碼 -
這個Job在執(zhí)行編譯后,會將這些參數(shù)在編譯時寫入App的asset目錄下的具體文件 -
測試下載增量包后進行手工測試,測試完畢后在App中的某個后門入口上傳覆蓋率文件 -
覆蓋率文件上傳后,觸發(fā)新的Jenkins Job,在上報的同時,App從文件中讀取前一步寫入到assert中的文件,將分支名、BaseCommitID和EndCommitID這些信息同時上報 -
新的Jenkins Job拿到這些信息后,根據(jù)上報的CommitID和分支名等信息,重新進行一次編譯,復刻第一次插樁包的編譯過程 -
這個Job編譯完成后,在腳本中繼續(xù)調(diào)用generateCoverageReport指令來生成報告,這個時候編譯的產(chǎn)物就和編譯插樁包的內(nèi)容一模一樣了
至此,整個流程就形成了一個閉環(huán),將最后一步生成的報告同步到公司內(nèi)部的展示平臺即可展示。
?起點在接入騰訊藍盾系統(tǒng)之后,不再使用Jenkins來做CI,但藍盾的整體流程與Jenkins基本類似,所以整個流程遷移藍盾后并不需要做太多的修改,只需要將Jenkins的Job,遷移到藍盾的流水線即可。
?
對于測試人員來說,他們的使用流程與之前并無太大區(qū)別,主要的修改在于使用精準化的Job來打出增量包,整體流程如下所示。
至此,我們就完成了從開發(fā)到測試,并支持完整CI流程的方案設計。
拓展增量實現(xiàn)
由于JaCoco Android Plugin在客戶端沒有增量的實現(xiàn),所以,我們需要單獨實現(xiàn)一套Plugin系統(tǒng),在借助JaCoco探針的插入機制基礎上,完成對增量代碼的探針修改。
通常在增量修改方案有以下三種:
-
增量方案1:在插樁時,對diff信息做增量,精確到行級別插樁 -
增量方案2:插樁時做全量插樁,在生成報告時對diff信息做過濾,在報告級別做行增量 -
增量方案3:插樁時對Class級別做diff過濾,并進行Class內(nèi)的全量插樁,在生成報告時,針對diff信息的行級別做報告的過濾
這里我們選擇的第三種方案來實現(xiàn)。這是一種性價比最高的方案,通過獲取diff信息,在插件層不用做太多的修改,只需要過濾掉非diff的文件,而在生成報告時,再借助diff行號來做高亮展示即可。
在Gradle插件中,我們在Transform過程中來獲取增量內(nèi)容,借助Git diff的執(zhí)行結果,過濾需要做增量的修改Class文件,相關代碼如下所示。
在獲取到增量Class文件之后,再借助JaCoco增加探針來實現(xiàn)探針代碼的織入,相關代碼如下所示。
增量探針的問題解決了,那么下面的問題就是如何獲取diff信息了。在Git中,我們可以很方便的通過git diff命令來獲取增量信息。
該命令格式如下所示,它用來對比不同commit(或分支)間的增量代碼:
git diff [<options>] <commit> <commit>
其中commit可以是分支名,也可以是commit的id,對比分支間的差異,可以簡寫為 git diff targetBranchName,表示對比當前分支與目標分支間的代碼差異。
?diff文件的解析這里不做過多的解釋,感興趣的朋友可以參考我的博客 https://xuyisheng.top/diff/
?
獲取到diff增量信息之后,就可以借助正則來獲取我們需要的信息了——diff文件,以及該文件中的diff行號。
修改Report生成邏輯
一般來說,修改JaCoco的Report生成邏輯,通常是通過修改JaCoco Analyzer類的analyzeClass函數(shù)。但實際上,JaCoco的Report庫,給我們提供了修改的接口。在修改之前,我們先來了解下JaCoco默認的Report機制。
原始報告解讀
JaCoco覆蓋率報告分為三層遞進數(shù)據(jù):Package——Class——Method,其覆蓋率統(tǒng)計實際上是由內(nèi)向外的。
先了解下場景,我們修改了兩行代碼,并執(zhí)行了其中一行。我們先看Method級別:
這里由于是增量代碼檢測,所以在MainActivity里面的所有代碼中,只有有代碼修改的兩個方法有覆蓋率數(shù)據(jù),即test2和test3。其中test2執(zhí)行了,所以其覆蓋率為100%,而test3未執(zhí)行,所以其覆蓋率數(shù)據(jù)未0%。
再來看看Class級別:
這里的覆蓋率是以Method為維度進行統(tǒng)計的,發(fā)生變更的有兩個Method,執(zhí)行的是一個,所以覆蓋率為50%。
同理,再外層的Package級別,則是以Class為維度進行統(tǒng)計的。
增量覆蓋率的統(tǒng)計
由于JaCoco的報告太過于復雜,而且與我們常規(guī)的理解有些不同,對于測試或者Reporter對象來說,有太多的無關緊要的數(shù)據(jù),所以,我們可以自己來編寫增量覆蓋率的統(tǒng)計規(guī)則。
在前面的插樁過程中,實際上我們已經(jīng)拿到了「修改的文件」——「修改的具體行號」這樣的一個對應關系文件,只要我們再讀出JaCoco覆蓋率文件中,這些修改的行,是否被執(zhí)行的數(shù)據(jù),其實就可以完成覆蓋率的統(tǒng)計了,我們簡單的定義「增量覆蓋率」的統(tǒng)計規(guī)則:
該文件中被修改的代碼中已執(zhí)行或者部分執(zhí)行的代碼行數(shù) / 該文件中被修改的所有代碼行數(shù) %
有了這些數(shù)據(jù)之后,我們也不需要再使用JaCoco的覆蓋率報表了,直接使用自己的統(tǒng)計數(shù)據(jù),而只需要最后跳轉每個文件的源代碼覆蓋文件即可。
那么現(xiàn)在的問題就剩下如何拿到行是否被執(zhí)行的覆蓋率數(shù)據(jù)了。
參考下官方的Report Demo,地址如下所示。
https://github.com/jacoco/jacoco/blob/master/org.jacoco.examples/src/org/jacoco/examples/ReportGenerator.java
核心代碼如下所示。
官方在Demo中給出了一個IBundleCoverage的結構化數(shù)據(jù),它就是從execution Data File中得到的所有覆蓋率的數(shù)據(jù),那么接下來,我們遍歷這個數(shù)據(jù),拿到其中的LineImpl信息,即可獲取當前行是否被執(zhí)行的數(shù)據(jù)了,再和我們的增量信息做對比,就可以完成增量覆蓋率的統(tǒng)計計算了。
修改報表
JaCoco的原生報表,結構比較復雜,不適合展示和匯總,特別是不利于非技術人員的使用,所以,我們需要對原生報表進行修改,讓報表更加直觀。
首先,我們來思考下,一個非技術人員究竟需要怎么看一個覆蓋率報表,假如我是這樣一個人,我想知道的,就是每個文件的增量代碼覆蓋率,說直白一點,就是這個文件里面,在這個需求中,我修改了哪些行,這些行里面,有哪些行是已經(jīng)執(zhí)行了,哪些是沒執(zhí)行的,如果能知道這個修改對應的修改人是誰,是在哪個需求里面改的,那就更好了,其它的內(nèi)容,我也并不關心。
所以,在梳理了覆蓋率真正的需求之后,我們抽象出了新的覆蓋率報表的必須項目:
-
文件名 -
已覆蓋了多少行 -
總共修改了多少行 -
提交信息 -
覆蓋率數(shù)據(jù)
這是匯總表,針對每個文件的詳情,我們可以繼續(xù)使用JaCoco的明細報表。
在官方Demo中,我們發(fā)現(xiàn),報表數(shù)據(jù)實際上都被解析到了IBundleCoverage中,所以,我們只需要遍歷IBundleCoverage數(shù)據(jù),就可以拿到JaCoco的統(tǒng)計數(shù)據(jù),IBundleCoverage的統(tǒng)計維度和它原生的報表結構類似,最外層是packages數(shù)據(jù),內(nèi)部遍歷是sourceFiles數(shù)據(jù),在到內(nèi)部,就能拿到行數(shù)據(jù),在行數(shù)據(jù)中,就可以拿到該行是否被覆蓋的狀態(tài),OK,順著這個思路,我們就可以自己來統(tǒng)計相關的覆蓋率數(shù)據(jù)了,在遍歷的過程中,我們可以使用一些Map來保存遍歷出來的數(shù)據(jù),最后輸出到文件中,通過自定義的HTML文件展示出來。
核心代碼如下所示。
這里有幾個細節(jié)需要注意:
-
sourceFiles的firstLine和lastLine,封裝了可執(zhí)行的所有代碼起始值(因為字節(jié)碼中是不包含import和package信息的),所以這樣會導致git diff信息中的import等修改不會被遍歷到,這部分數(shù)據(jù)默認為已執(zhí)行,需要手動處理掉 -
前面分析了我們的增量邏輯,在插樁時,只對文件做增量,所以這個文件主要有修改,那么整個文件都會被插樁,而在JaCoco生成每個文件的覆蓋率信息時,需要對行做過濾,將不在diff文件中的行設置為Empty,取消覆蓋率的高亮顯示 -
在統(tǒng)計覆蓋率時,我們進行了包容處理,即只要不是完全未執(zhí)行的代碼,我們都認為是已執(zhí)行,這樣做的原因主要是因為PARTLY_COVERED在Kotlin中經(jīng)常會因為一些語法糖而產(chǎn)生一些無效的標識,而且有些場景下,一個條件分支的部分執(zhí)行,就已經(jīng)完成了邏輯的處理,所以,這樣的計算也是合理的
最終我們會把遍歷出來的數(shù)據(jù)寫入一個文件,這樣再通過自定義的HTML文件展示這個一個數(shù)據(jù)文件即可,這里還有一點需要注意,那就是HTML不能訪問本地的文件系統(tǒng)(跨域問題),所以這里使用import js的方式引入數(shù)據(jù)文件。
上面這張圖就是最終生成的覆蓋率報告,點擊相關類,就可以跳轉到相關的類詳細覆蓋率文件界面,這里是復用的JaCoco覆蓋率展示頁面,這里不再贅述。
精準化測試的作用
精準化測試對開發(fā)和測試的收益如下:
-
將黑盒測試轉化為白盒測試 -
統(tǒng)計到行,提高了發(fā)現(xiàn)問題的精讀和效率 -
提升了測試回歸用例的效率 -
反向約束了代碼規(guī)范
在精準化測試平臺的迭代過程中,通過覆蓋率文件的分析,發(fā)現(xiàn)了很多在一般測試流程中很難發(fā)現(xiàn)的問題,下面簡單的列舉一些在測試過程中發(fā)現(xiàn)的問題。
場景展示
-
「更多」按鈕的點擊事件和無actionurl時隱藏的邏輯沒覆蓋
-
代碼冗余,同樣的代碼,方法名不同,且未被調(diào)用
-
代碼邏輯不對,網(wǎng)絡異常后無需再綁定數(shù)據(jù)
上面這些案例就是通過精準化測試平臺發(fā)現(xiàn)的一些問題,這些問題大致可以分為下面幾類。
-
代碼分支未覆蓋,該部分邏輯未得到驗證 -
代碼冗余,開發(fā)可以發(fā)現(xiàn)潛在的不規(guī)范代碼 -
修改了與提交信息不同的修改,場景不會被測試用例覆蓋
銀彈?
在開發(fā)精準化測試平臺的過程中,我們也發(fā)現(xiàn)了這個方案的一些不足,這也印證了那句老生常談的話——軟件開發(fā)領域沒有永恒的銀彈。
首先,精準化測試在沒有自動化測試平臺的基礎上,如果測試代碼頻繁修改,那么會導致增量覆蓋率文件存在一些問題。因為精準化測試報告與代碼版本是一一對應的關系,這就好比是醫(yī)院的驗血報告,報告只對當次提交的血液檢測樣本負責,覆蓋率報告也是同樣的道理。
其次,精準化測試平臺在多人協(xié)作的場景下有一些不足,覆蓋率記錄文件在本地設備中,所以多人協(xié)同測試的場景下,本地覆蓋率文件難以合并。
最后,精準化測試報告的分析依賴人工,需要測試和開發(fā)共同分析報告,找出潛在的問題風險。
介于上面提到的問題,我們梳理了當前精準化測試平臺的使用規(guī)范和適用場景。
-
在集成測試階段,通過精準化測試平臺,嚴格把控新的代碼提交,保證合入代碼合格且經(jīng)過驗證 -
在功能測試階段,盡可能單人單業(yè)務測試,針對每次測試的精準化報告,來分析潛在的風險,和發(fā)現(xiàn)潛在的問題 -
通過自動化測試,回歸已測場景,從而產(chǎn)出迭代的覆蓋率報告
同時,基于這些問題和限制,我們也在進一步拓展精準化測試平臺的功能,在后續(xù)迭代中,將在下面幾個方面來逐漸提高精準化測試平臺的功能,爭取從「能用」到「可用」再到「好用」,將精準化測試平臺打造成提升敏捷開發(fā)效率的利器。
-
增加代碼調(diào)用鏈管理分析工具和測試用例數(shù)據(jù)庫的搭建,為搭建「代碼」——「測試用例」的映射關系做準備 -
搭建自動化測試平臺,進一步降低測試的回歸工作量,同時利用自動化測試,降低精準化測試覆蓋率數(shù)據(jù)的生成難度 -
自動化分析覆蓋率報告,智能識別未覆蓋代碼,通過「代碼」——「測試用例」映射庫,識別未覆蓋的測試用例,提交給測試做進一步的驗證
以上。
在此感謝起點客戶端團隊和起點測試團隊在精準化測試平臺的搭建中提供的寶貴建議和幫助。
向大家推薦下我的網(wǎng)站 https://xuyisheng.top/ 點擊原文一鍵直達
專注 Android-Kotlin-Flutter 歡迎大家訪問
往期推薦
更文不易,點個“三連”支持一下??
