代碼質量提升之道——代碼覆蓋率原理與移動端工程實踐

一、背景介紹

隨着項目迭代的不斷深入,工程邏輯與用戶場景日益複雜,傳統的白盒測試體系已經無法適應苛刻的工程質量要求,因此有必要針對工程質量進行精細化管理。質量評估不再單純依賴bug率和性能指標,而是通過精準的數據來量化代碼質量,代碼覆蓋率就是其中的一項重要標準。

簡單來說,代碼覆蓋率就是單元測試或者UI測試過程中對於被測代碼的覆蓋程度,可分爲以下三種度量方式:

  • StatementCoverage:最基礎的一種覆蓋方式,用以度量被測代碼中每個可執行語句是否都被執行。可執行語句不包括頭文件聲明、宏定義、代碼註釋等
  • Branch Coverage:分支覆蓋,度量程序中每一個判定的分支是否都被測試
  • Loop Coverage:度量對循環體進行了多少次覆蓋

在實踐中,通常採用Statement Coverage方式來分析覆蓋率數據。拿到覆蓋率數據之後,分析未覆蓋部分的代碼,可以反推出測試是否充分,前期的測試用例設計是否合理,沒有覆蓋到的代碼是否是設計的盲點。同時其對於冗餘代碼的檢測具有重要的參考意義, 可以逆向反推出代碼設計中存在的問題,提醒開發人員理清代碼邏輯關係,提升代碼質量。

代碼覆蓋率高不能說明代碼質量高,但是代碼覆蓋率低,意味着代碼質量很可能存在一些問題,因此代碼覆蓋率可以作爲代碼質量的一個重要衡量標準

二、原理與演示

獲取代碼覆蓋率的前提是插入計數器,也就是插樁,從直觀上講,可以分爲三種方案:

  • 源代碼插樁:直接在源碼中的每行可執行語句中加入計數器。該方案開發成本太高,且影響包大小和運行時性能
  • 中間文件插樁:在編譯過程的中間文件插入彙編代碼,成本低,速度快,是理想的插樁方案
  • 可執行文件插樁:目前暫時沒有合理的方案。

因此,我們採用中間文件插樁方案,過程如下圖所示:

源代碼文件首先經過編譯預處理,之後編譯成彙編文件,在生成彙編文件的同時完成插樁。每個樁點插入若干條彙編語句,直接插入生成的*.s文件中,最後將彙編文件生成目標文件。在程序運行過程中樁點負責收集程序的執行信息。

以源文件test.c爲例:

預處理命令(gcc -E test.c -otest.i)之後,生成test.i文件,對該文件進行編譯(gcc-fprofile-arcs -ftest-coverage -S test.i),生成test.s彙編文件。可以看到gcc是通過增加編譯選項-fprofile-arcs -ftest-coverage來進行插樁。

打開彙編文件test.s,其中可以看到代碼運行過程中計數器增加的邏輯:

上圖中展示了計數器自增的過程,將llvm_gcov_ctr賦值給寄存器rax,之後將rax加1,最後將rax賦值給llvm_gcov_ctr。

除了上述邏輯,彙編文件中還列出了覆蓋率收集的入口方法__gcov_init, 該方法中的邏輯爲:

在可執行文件進入用戶代碼段main函數之前,先調用__gcov_init函數初始化統計數據區,即將所有文件的gcov_info結構組織成一個鏈表,鏈表頭就是gcov_list,其爲全局鏈表,將在輸出統計數據時應用。

之後__gcov_init調用atexit (gcov_exit)將gcov_exit函數註冊爲exit handlers。

App運行完調用exit正常結束時,gcov_exit函數就會得到調用,其內部調用gcov_flush函數輸出統計數據到 *.gcda 文件中,也就是從gcov_list的第一個gcov_info結構開始,爲每個被測文件生成一個.gcda文件。.gcda文件的主要內容就是gcov_info結構的內容。

插樁之後,會生成test.gcno文件。運行可執行文件後,生成test.gcda文件。

最後,運行gcov test.c命令可生成test.c.gcov文件,如下所示:

每行前面的數字表示被執行次數,####表示該行未被執行,需要重點關注。

由上文可以看出,覆蓋率數據分析需要三類文件:

  • 源代碼文件
  • gcno:包含基本的塊信息,以及代碼行與塊的映射關係,也就是保存計數插樁位置和源文件之間關係
  • gcda:包含代碼行執行的情況,以及覆蓋率的信息歸納

其中gcno和gcda文件可以看做是覆蓋率分析的中間文件。如果出於保密要求,無法提供源文件,也可以分析出總體的行覆蓋率和方法覆蓋率,只不過無法將覆蓋率與代碼邏輯進行一一對應。

三、iOS平臺的接入

下文將以iOS平臺爲例,探討代碼覆蓋率在工程中的具體實踐。

目前,XCode已經提供了運行時覆蓋率的展示,但是現有覆蓋率存在以下問題:

  • 只支持調試模式下的單元測試覆蓋率
  • 無法離線獲取覆蓋信息
  • 無法合併多個設備、多個版本的覆蓋信息

接下來介紹如何解決這些問題。

1.工程插樁

在待統計Target的Build Settings中分別設置Instrument Program Flow、Generate Legacy Test Coverage File爲True,即可快速打開插樁。需要注意的是,爲了控制包大小和運行性能,一般只在Debug環境下進行插樁。

2.模擬器與真機的區別

插樁打開並完成編譯後,在編譯緩存目錄下會生成與源文件對應的gcno文件。

App運行後,通過在某一時機(如切入後臺時,或者定時處理)調用__gcov_flush()方法將覆蓋信息寫入gcda文件。模擬器下,gcda與gcno文件處於同一目錄。真機中,gcda文件會在沙盒生成,需要設置沙盒中的目錄名稱和path深度,如下所示:

3.系統架構

針對代碼覆蓋率的多種使用場景,我們設計出一站式解決方案,架構圖如下所示:

其中底層依賴於OS及其對應的插樁工具,如gcc。上層文件系統包括編譯插樁配置,預植入的git hook定製腳本,App分發機制。該層爲覆蓋率中間文件的生成提供技術支持。

數據採集主要是將分散在打庫機和各設備中的覆蓋率中間文件收集起來。打庫機提供了編譯時生成的gcno文件並上傳,帶有覆蓋計數器的App運行時產生了gcda文件並上傳,數據歸集模塊則將二者按照版本號、設備ID等標識一一對應、等待分析。

全量分析是針對某時刻全部源碼所對應的覆蓋率數據,通常需要經過過濾、容錯、分析、合併四個環節,該過程部分依賴於lcov工具。lcov最終將gcno與gcda文件解析爲中間info文件,該文件與對應的源代碼文件結合在一起,可以定製生成詳細的報告文檔。

增量分析是針對某段時間或版本所產生的增量代碼所對應的覆蓋率數據。爲了降低開發成本,增量覆蓋率是由全量覆蓋率經過源碼差異映射、增量過濾、增量分析而產生。

由於已分析輸出的全量和增量覆蓋率皆爲文本文件,可讀性不佳,無法快速查詢代碼細節。因此,有必要增加報告層。現有的報告層可以根據默認模板或自定義模板輸出對應的XML和H5類型的完整報告。

四、使用場景

1.版本迭代全量與增量覆蓋率

其中某個模塊所對應的全量覆蓋率數據展示如下:

有了全量覆蓋率,可以通過增量分析模塊獲得增量覆蓋率數據,默認展示樣式可以參考上圖。

通過查詢詳細的覆蓋率報告,代碼和模塊覆蓋率偏低的情況的產生一般有如下原因

  • 受後臺數據等影響,在當前版本中未打開代碼邏輯開關
  • 代碼本身邏輯:如父類的方法被子類覆蓋,導致父類代碼無法被運行
  • 冗餘代碼:可以通過報告快速確認冗餘代碼範圍,以及不屬於本模塊的代碼
  • 部分彈框、浮層等需要全新安裝(而不是覆蓋安裝)或者一定時機觸發的場景,需要評估是否有必要專門建立滿足場景觸發的case

積累了數個版本的全量覆蓋數據,就可以進行預警機制建設。輸出每日開發自測、測試人員手動測試、自動測試覆蓋率,分析合理的增長趨勢。同時整體考量以下因子:前置產品、資源與接口、開發進度、測試進度、bug產生與修復進度、發版後的bug追溯機制。如果偏離該趨勢、進度延誤或者線上bug復現,則及時進行預警。

2.代碼審覈增量覆蓋率

提交的增量覆蓋率示例如下:

其中列出了本次提交的代碼變更行數、被覆蓋的行數以及增量覆蓋率。中間位置詳細列出了未被覆蓋的行號,點擊之後會立即定位到相應的代碼行。

開發者提交代碼前,先在模擬器或真機上自測。commit時,預先植入git hook中的腳本會自動進行提交代碼的增量覆蓋率分析,生成報告文檔並上傳服務端,得到一條覆蓋率遠程url。commit後,commit message末尾會自動附加該url。

本次提交push之後,代碼審覈人員可以參考該覆蓋率數據。該過程對開發者透明,沒有接入成本。審覈人員根據該數據可以快速定位開發自測的充分性和有效性。同時支持根據需要設定提交代碼覆蓋率的合法閾值,未達到該閾值可以直接拒絕提交或增加提示。

3.單元測試覆蓋率

單元測試的質量評估主要依賴於測試用例的成功率和代碼覆蓋率。因此我們推動了各模塊發版前覆蓋率檢查、自動驗證流程的建設。推動垂線模塊合入代碼前的有效性驗證,包括bug修復率、線上運行的覆蓋率統計報告,最終目標是單元測試0錯誤率,覆蓋率超過60%時才允許合入代碼庫。

4.自動化測試覆蓋率

通過分析自動化測試執行過程中的覆蓋率數據,可以建立起測試用例與程序代碼之間的邏輯關係。

對於開發人員來說,可以看到測試人員執行用例的代碼細節有助於進行缺陷的修復,測試數據可以直接爲開發調試提供依據、快速定位並修復缺陷。

對於測試人員來說,通過分析每條測試用例對覆蓋率的貢獻可以精簡用例集、剔除無效用例。測試人員通過修改的源代碼快速確定測試用例的範圍,減少迴歸測試的盲目性和工作量,快速修訂測試用例,達到測試覆蓋率最大化,從而提高測試效率。

五、總結

通過引入代碼覆蓋率分析體系,可以精確把控增量代碼質量,持續改善優化存量代碼。同時將測試用例的影響範圍細化到代碼層面,從而實現精準化測試。目前這套體系作爲一項開發流程規範進行實踐,如何在實踐過程中減少人工參與的成本、進行用例的智能推薦或生產,將是我們下一步工作的重點。

本文轉載自公衆號愛奇藝技術產品團隊(ID:iQIYI-TP)

原文鏈接

https://mp.weixin.qq.com/s?__biz=MzI0MjczMjM2NA==&mid=2247485454&idx=1&sn=f78746966253638878ba3a7ffa396e10&chksm=e976942dde011d3ba81b8d8db811c1f20cc9a3e8dfc63e8a4914209b10677ebaea3ec8e44804&scene=27#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章