全棧程式碼測試覆蓋率及用例發現系統的建設和實踐

語言: CN / TW / HK

本文首發於微信公眾號 “Shopee 技術團隊

1. 背景

隨著專案不斷深入迭代,業務邏輯以及使用者場景日漸複雜,補充和維護單元測試維護的成本也變得越來越高。測試覆蓋質量通過測試用例評審或者人工 Code Review 的方式費時費力,單憑多方溝通和經驗累積的方法,往往不夠準確,也難以避免開發人員存在在程式碼上線前“夾帶私貨”的場景,並且沒有量化的、直觀的客觀資料來支撐。

為了在有限的時間及人力成本內保證專案質量,實現對專案質量的精細化管理,我們研發了 Finder —— 全棧程式碼測試覆蓋率及用例發現系統(下文簡稱 Finder),通過精確化的資料量化程式碼質量,從而實現精準化測試。本文將介紹 Finder 的總體架構,以及它作為質量保障體系中的重要一環,如何在專案中實現精確化測試。

2. Finder 專案介紹

Finder 主要分為兩個模組,一個是測試過程中對程式碼測試覆蓋率進行收集與統計,一個是分析程式碼和用例的對映關係,精準確定迴歸測試範圍

程式碼測試覆蓋率統計模組,能夠滿足多環境、多需求、多服務的複雜測試場景,實時收集測試過程中的覆蓋率資訊,並生成覆蓋率統計報告;其支援多端語言接入(Web、React Native、Golang),實現前後端專案全覆蓋,打通整體研發流程。

相關用例發現模組,針對變動的程式碼分析函式呼叫關係,追溯完整的呼叫關係鏈路,標記出所影響的 API 介面及相關用例,確定測試迴歸範圍。

3. 架構設計

Finder 分為代理層、應用層、核心服務層三個模組:

  • 代理層 Finder Agent,負責前期資料採集的工作,包括編譯階段的程式碼插樁、覆蓋率資料的採集、解析原始碼函式呼叫關係資訊等;
  • 應用層 Finder Platform,包含需求資訊管理、分支資訊管理、單檔案覆蓋率染色圖展示、API 資訊展示、測試用例關聯、函式呼叫鏈展示等頁面模組;
  • 核心服務層 Finder Server,其中,覆蓋率分析和服務呼叫分析是最主要的兩個模組:
    • 覆蓋率分析,對收集到的覆蓋率資料進行資料聚合、差異增量分析、資料修正等操作;
    • 服務呼叫分析,將原始碼解析工具傳輸來的資料進行資料結構轉換後,完成呼叫拓撲圖生成、API 資訊關聯、測試用例發現等操作。

在前期完成資料採集的接入工作後,測試人員無需額外操作,正常進行業務測試,測試完成後可以在視覺化平臺中檢視覆蓋率資料、相關測試用例等資訊。

4. 實現方案

4.1 程式碼測試覆蓋率模組

程式碼覆蓋率模組主要分為兩個步驟,第一步是通過對專案原始碼插樁,採集覆蓋率資訊;第二步是對覆蓋率資訊進行分析,通過視覺化平臺展示統計報告

步驟一:插樁與資料採集

程式碼插樁,意為在程式中插入一些程式碼,用於跟蹤被測程式的某些資訊。對於覆蓋率測試而言,插樁的目的是檢測程式中可執行語句被執行(即被覆蓋)的情況。

Finder 支援 JS 和 Golang 兩種程式語言的程式碼覆蓋率統計,接入成本低,對業務需求無侵入性。

  • 對於 JS 程式,我們在 babel 編譯階段進行插樁操作,注入到全域性物件 window 中。我們提供了上報覆蓋率資料的 npm 包 —— coverage-report,前端應用接入後會主動上報資料到 Finder Server;
  • Golang 程式的覆蓋率接入對業務專案零侵入,只需要引入 Finder Agent —— 覆蓋率收集工具:它在編譯階段對原始碼插樁,包括 Git 資訊和覆蓋率資訊注入;插樁完成後的服務會啟動一個統計覆蓋率的 Http Server,Finder Server 通過該 Http Server 提供的介面定時請求覆蓋率資料。

步驟二:差異覆蓋率統計

比起全量程式碼的覆蓋率,實際上我們更關心改動程式碼部分的覆蓋率情況。可以通過 Git 指令對比功能分支與 master 分支的程式碼差異,過濾無關程式碼,只針對改動程式碼部分進行覆蓋率統計。

為實現差異覆蓋率的計算,我們約定以行(line)作為維度的結構表示 Git 程式碼差異和覆蓋率資訊,滿足兩者一一對應的關係。

首先將 Git 分支對比得到的 Diff 資料轉換成以程式碼行作為索引的陣列格式,元素的值有空值和 + 兩種列舉值

  • 空值代表沒有改動;
  • + 代表有改動。

覆蓋率陣列也轉換成以程式碼行作為索引的陣列格式,元素的值有 -1、0、1 三種列舉值:

  • -1 代表無需統計覆蓋率的程式碼(例如空格,註釋等);
  • 0 代表未覆蓋的程式碼(尚未執行);
  • 1 代表已覆蓋的程式碼(已被執行)。

將 Diff 資料與覆蓋率陣列進行合併,將非改動程式碼的覆蓋率值置為 -1 。得到最終的差異覆蓋率資料。

關鍵問題一:資料來源標準化

由於不同程式語言上報的覆蓋率資料結構都不一致,我們需要將資料來源標準化,統一轉化以行(line)為維度的陣列結構

首先,前端應用上報的覆蓋率資料包含三種維度的覆蓋率資料:

  • Statement Coverage:語句維度的覆蓋率資料;
  • Branch Coverage:條件維度的覆蓋率資料,例如 if/else、switch、三元運算子等;
  • Function Coverage:函式維度的覆蓋率資料。

對於這三類覆蓋率,我們做出以下資料轉換:

我們採用與操作將三類覆蓋率資料合併為行覆蓋率,即一行程式碼滿足三類覆蓋率才算被執行過。

而後端 Golang 服務採集的覆蓋率資料,如下圖。

同樣,我們也需要將其轉換為行維度的陣列結構。

關鍵問題二:確保程式碼變更後的覆蓋率正確性

在整合測試的過程中,開發人員不可避免地會進行程式碼更新。當代碼更新後,我們希望能夠記錄新程式碼改動覆蓋率的同時,依然保持原有程式碼的覆蓋率資料。所以我們需要獲取新舊提交之間的程式碼差異,對原有覆蓋率資料進行“修正”操作:

對原有的覆蓋率資料,先刪除 diff 結果中被移除的程式碼對應的覆蓋率資料,再插入 diff 結果中新增的程式碼對應的覆蓋率資料。

舉個例子:

對比首次提交 commit 1 和 master 分支,得到首次改動:新增三行程式碼。

標記這三行程式碼的行索引 3、4、8,並統計它們的覆蓋率。

對比 commit 1 和 commit 2,得到兩次提交之間的改動:刪除之前兩行程式碼,新增一行程式碼。

移除原資料中第 3、4 行資料,然後插入新改動程式碼的行索引 7(刪除後的索引位置)並統計新改動的覆蓋率。

修正後的結果和 commit 2 與 master 分支對比的結果一致,既保留了首次改動的覆蓋率資料,也記錄了新改動的覆蓋率資料。

關鍵問題三:單環境的程式碼覆蓋率採集

在實際開發流程中,研發環境只有一套,我們往往不能獨佔它,所有周版本需求都在 staging 環境開發,在 test 環境測試。這樣會出現一個問題:由於這種單一環境的測試流程,導致統計出來的覆蓋率無法區分來自哪個具體功能分支,進而影響對各功能需求的測試範圍判斷。

為了解決這個問題,我們提出兩個方案:

第一個方案是搭建多泳道研發環境,通過 PFB(Per Feature Branch)的形式對各需求開發環境進行隔離。PFB 是一個可以快速搭建虛擬環境的工具。它支援將多個功能分支版本同時部署在同一個測試環境中,且每個功能分支可擁有單獨的流量訪問,從而實現不同的功能分支可擁有相對隔離的虛擬環境。

但是由於這個架構改動較大,距離落地到專案中還需一段時間,因此第二個方案應運而生:我們對單一的整合分支進行功能追溯,判別出變動程式碼是屬於合併進來的哪一個功能分支,並進行分類展示

  • 通過 Git Blame 命令從整合分支中判別出到各行程式碼對應的 commit hash;
  • 通過 Git Branch 命令查詢 commit hash 來自哪一個功能分支。

通過以上兩步尋找到每行程式碼與功能分支的對應關係,與覆蓋率結果進行對映後可得出對單一整合分支的功能追溯效果,解決單環境的程式碼覆蓋率採集問題。

4.2 相關用例發現模組

相關用例發現模組的關鍵是通過追溯變動程式碼的呼叫關係鏈,確定測試迴歸範圍。這個過程主要分為兩個階段:原始碼解析階段資料分析階段

步驟一:原始碼解析

我們在 Gitlab CI 接入原始碼解析工具,當有新程式碼提交時,觸發原始碼解析。

通過對抽象語法樹 AST 和中間轉換程式碼 SSA 進行解析,得到以下資料:

  • 專案 Git 資訊
  • 函式基本資訊
  • 函式呼叫關係
  • RPC 呼叫資訊
  • API 資訊

最後將這些資料都打包統一發送到 Finder 服務端。

步驟二:資料分析

Finder 服務端接收到解析資料後,進行差異增量程式碼的函式呼叫關係全鏈路追溯,最後標記出相關的 API 資訊及測試用例。具體追溯流程如下:

  1. Git Diff 得到功能分支與 Master 分支對比的程式碼改動;
  2. 通過差異增量程式碼的所在行數,確定所屬函式體,進入步驟 3;
  3. 追溯指定函式的呼叫關係,直到尋找到 API 觸發函式;
  4. 若遇到 RPC 呼叫的情況,進行 RPC 呼叫關係追溯,找到 RPC 呼叫方函式,繼續回到步驟 3 的操作;
  5. 追溯工作完成,得到從變動函式到 API 觸發函式的呼叫關係全鏈路;
  6. 通過以上得到的 API 觸發函式,標記相關的 API 資訊及測試用例。

每次程式碼變更都會觸發上述流程,得到最新的函式呼叫鏈路資訊,在視覺化前臺中展示該功能分支的變動函式呼叫關係鏈,以及相關測試用例。

關鍵問題:後端微服務架構串聯,全鏈路覆蓋

目前我們後端服務採用了分散式的微服務架構,例如一個購買商品的流程,當用戶進入 APP 瀏覽商品時,會發起請求經過閘道器層,轉發到商品服務,然後在建立訂單時會調起訂單服務,訂單服務再呼叫使用者資訊服務和促銷服務,整個過程還會呼叫多個基礎服務共同完成一次操作流程。

我們希望除了解析服務內的呼叫關係外,也需要服務間的呼叫關係串聯起來,得到完整的呼叫鏈路。

為了解決這個問題,我們針對專案中的 RPC 呼叫關係進行靜態解析,通過尋找呼叫方函式——RPC Command——被呼叫方函式三者的關聯關係,對呼叫方函式和被呼叫方函式進行標記,當函式呼叫關係鏈追溯到被呼叫方函式時,匹配到其對應的呼叫方函式,繼續向上追溯,從而得到服務間的完整呼叫關係鏈路。

5. 實踐應用

Finder 通過接入團隊中的專案流程管理系統,在整個需求生命週期的各個環節發揮著不同的作用。

5.1 提測階段

5.1.1 覆蓋率提測達標

開發人員必須達到規定的覆蓋率標準才能提測,對於覆蓋率未達標的情況,開發人員需要反推程式碼邏輯是否存在問題,進一步分析前期技術方案設計不夠合理,還是對技術方案的實現有誤,或者是在實現過程中造成的策略性放棄等。

5.1.2 覆蓋率定製化

針對不同專案會有定製化的覆蓋率要求,支援配置需要忽略統計覆蓋率的檔案型別以及目錄。

對於移動端應用,提供 iOS 和 Android 雙平臺的覆蓋率資料統計,這樣能夠提前暴露移動端的相容性問題,保證提測交付質量。

5.2 測試階段

5.2.1 覆蓋率實時分析

測試人員介入測試後可以在 Finder 平臺上實時檢視覆蓋率分析結果,瞭解該測試版本變更程式碼的測試覆蓋情況。

5.2.2 覆蓋率染色圖

通過程式碼覆蓋率染色圖輔助整個測試過程,方便測試人員對測試用例進行查漏補缺,儘可能保證所有關鍵場景都能覆蓋到;同時可以驅動測試人員加強對程式碼的理解,充分把控程式碼質量。

5.2.3 需求維度覆蓋率統計

通過關聯專案管理系統,從需求級別的角度,提供該需求下各模組的覆蓋率資料,包括不同的後端服務和前端應用,輔助測試人員做決策,衡量質量風險。

5.3 迴歸階段

標記變更範圍

根據程式碼變更範圍推斷出函式變更範圍,進一步標記出相關測試用例,從而實現用例和程式碼的關聯關係。

用例發現分析報告中會標記出該需求變更部分的相關資訊,包括變更的函式呼叫關係鏈路和受影響的 API 介面,測試人員從中可以快速確定迴歸測試範圍,通過更小的維度更精準地度量測試質量,針對性進行自動化/手工測試,極大減少迴歸階段的工作量,以較小的迴歸成本保證較高的測試質量。

6. 未來展望

Finder 作為專案中質量保障體系中的一環,目前還處於初級階段。在未來我們會持續尋找 Finder 在研測協同體系中的更優實踐。

1)支援更多維度的覆蓋率資料統計

我們希望在統計方面可以提供更產品化更精細化的資料,包括關鍵字覆蓋率、相關程式碼覆蓋率、高風險覆蓋率等,能夠更好地輔助測試人員做決策。

2)精準記錄 Bug 軌跡

後續我們打算支援通過以新增標記的形式單獨統計覆蓋率,通過記錄 bug 的完整程式碼軌跡,還原問題的復現路徑,精確定位問題所在。

3)建立用例知識庫

構建用例與程式碼對映關係,形成用例知識庫,支援對程式碼和用例進行關聯管理,精確推薦改動程式碼關聯的用例,提供測試用例優化建議。

本文作者

Hao、Qianguang,前端工程師,來自 Shopee Digital Purchase 團隊。