Android 呼叫鏈——自動化精準測試

語言: CN / TW / HK

作者:位元組跳動終端技術——吳思成

一、背景自動化精準測試是指對每次 MR 中改動部分的程式碼,能夠進行自動的準確的測試,從而提高程式碼的質量保障以及減少測試的人耗。

1.1 現有流程

常規的開發流程如下:

為了確保這些變動不會引入 crash,影響線上使用者體驗,因此需要對這些變動進行測試。

測試一般分為開發同學白盒自測以及測試同學黑盒測試

目前測試的流程如下:

除去開發自測之外,還需要測試同學來進行測試,常規的測試手段就是針對應用的每個  Activity 維度去錄製測試用例,在每次提交mr的時候,測試的同學跑一下測試用例即可。

對於每次提交 mr,我們對程式碼所發生的變動抽象為如下三種情況:

  1. 添加了新的方法
  2. 改動原有方法
  3. 刪除方法

對於第一種情況,可能是添加了新的邏輯,也可能是新增了功能,因此現有的測試用例可能無法覆蓋到新功能,需要測試同學補充錄製測試用例。

對於後面兩種情況,無論是方法的邏輯修改還是方法刪減,現有的測試用例能夠覆蓋程式碼改動邏輯,因此測試的同學只需執行現有的測試用例即可

1.2 自動化測試流程

手動執行已有的測試用例其實是一個重複機械的工作,因此我們把這個流程改造成了自動化流程:

如上圖所示,我們把自動化測試新增到了 CI 流程當中,依賴於「構建包」任務獲取 apk 包。並且還使用到了公司內部的雲真機平臺,即我們可以直接通過 http 請求介面讓測試機執行我們的自動化測試指令碼,從而執行測試用例。

雲真機平臺介面

實際流程跑通之後,很快我們遇到了一些新的問題:

  • 像抖音、頭條的團隊,每天都有成百上千個 mr,如果每個 mr 都全量跑測試用例,那麼每個測試會特別耗時且極其耗費雲真機資源
  • 此外,其實大部分 mr 的程式碼改動量並不大,每次可能只涉及幾個函式的變動,因此使用全量測試用例顯然不合理

因此我們需要針對每次 mr 去尋找合適的測試用例,精準的推薦到自動化測試流程當中。

二、精準測試方案

問題描述

我們希望在每次 mr 的時候,能夠推薦和本次 m r變更程式碼相關的測試用例,從而進行自動化測試。

那麼需要面臨如下幾個問題:

  1. 如何將測試用例和程式碼關聯
  2. 如何獲取每次 mr 的變更內容
  3. 如何精準推薦測試用例

2.1 測試用例如何關聯程式碼

測試用例實質是黑盒測試時的點選輸入等一系列使用者行為的錄製,那麼我們如何能夠將這些使用者行為和實際的程式碼對應上呢?

連結點其實就在 Activity 上,上文提到錄製測試用例時,測試同學是以 Activity 作為維度進行錄製的,那麼如果我們能夠知道當前的程式碼關聯哪個 Activity,就可以只用這個 Activity 的測試用例來測試這段程式碼,能夠有效的減少測試案例的數量,提高測試的效率以及精確度。

那麼問題來了,我們如何知道某段程式碼關聯哪個 Activity 呢?

這裡可以通過生成方法呼叫鏈來實現。

2.1.1 什麼是方法呼叫鏈

就是將一段程式碼中的所有函式的呼叫關係通過呼叫邊連線形成圖,這個圖就是方法呼叫鏈圖

2.1.2 Android 呼叫鏈

如果能夠找到 Activity 的直接關聯的函式,並且結合方法呼叫鏈,我們就能夠找到 Activity 所間接關聯的函式。

如圖,function1 是 ActivityA 直接關聯的函式,那麼 function1 這條呼叫鏈上的其它函式都間接地與 ActivityA 關聯。

我們稱這種具備 Activity 到函式的邊的圖為 Android 呼叫鏈圖,下文中我們會著重地介紹如何生成一個 Android 呼叫鏈圖。

Android 呼叫鏈應具備能力:

  • 從 Activity 查詢所有該 Activity 涉及的函式(無層級關係)
  • 從 函式 查詢所有涉及該函式的 Activity
  • 查詢函式呼叫關係,一跳,二跳等
  • 查詢某個 Activity 的起始函式
  • 查詢某個 Acitivity 的下一個 Activity

2.2 獲取 mr 變更的內容

可能有人會想:獲取變更內容,難道不是求一下 mr 前後 commit 的 diff 就完事了嗎?

但其實並沒有這麼簡單,因為我們求出來的 diff 只是增刪改的程式碼段,而單憑程式碼段是沒有辦法通過 Android 呼叫鏈關聯到 Activity 的。Android 呼叫鏈的節點是方法,因此我們實際需要的 mr 變更內容應該是本次mr中發生變更的方法,這裡指的方法變更包括:

  1. 方法新增
  2. 方法改動
  3. 方法刪減

那麼我們如何知道一次 mr 中有哪些方法發生變動呢?

這裡我們使用到了靜態分析的技術,首先獲取本次 mr 中所有發生變更的原始碼檔案,以及其對應的變更前的原始碼檔案。然後通過 intellij 的 sdk 將原始碼檔案轉化為 psi,最後通過對比 psi 能夠獲取變更的方法有哪些。

PSI:程式結構介面,是IntelliJ Platform 中的一個語義抽象層,負責解析檔案並建立支援平臺許多功能的語法和語義程式碼模型。我們可以簡單的把它理解為是一個抽象語法樹,但是它基於java以及kotlin的語言特性做了更細粒度的解析,能夠識別出程式碼中的類、方法、引數、判斷符等語義。

因此基於psi,我們比較兩個檔案中方法是否發生了變更就會簡單很多,比較規則如下:

  1. 新增方法,比較新檔案和舊檔案中的方法名,如果某方法只在新檔案中存在,而舊檔案中不存在,則表示該方法為新增方法
  2. 刪除方法,比較新檔案和舊檔案中的方法名,如果某方法只在舊檔案中存在,而新檔案中不存在,則表示該方法為刪除方法
  3. 改動方法,如果新舊檔案中都存在該方法,那麼分別計算出新舊檔案中該方法的 body 的 size,如果 size 不一致,則表示方法發生了變動

2.3 精準推薦測試用例

對於測試用例的推薦,並不僅僅只是過濾出相關的 Activity 用例,還會結合 Activity 與這次變更的相關性、Activity 是否是線上熱點 Activity、Activity 的發現關聯 Crash 的後驗概率等資訊,去設定 Activity 的測試步數。此外,還會基於測試覆蓋率、crash 率、線上使用者機型分佈等多維度資料對目標 Activiy 的測試機型進行分配。具體的推薦演算法流程目前暫不便於對外,敬請期待後續的分享。

三、Android 呼叫鏈構建流程

3.1 階段一:生成全域性函式呼叫鏈圖

簡單介紹一下知識背景,呼叫鏈是基於靜態分析技術實現的,靜態分析技術可簡單分為原始碼分析和產物分析,例如 Android 所提供的 Lint 檢測就是基於原始碼分析,而這裡生成呼叫鏈是基於 apk 分析,也就是產物分析。

目前針對 Java 開源的靜態分析框架,主要有 wala 和 Soot,相比 wala,Soot 的文件更多,社群更為活躍,因此我們最終基於 Soot 進行定製開發

我們所開發的 Android 精準呼叫鏈生成工具——ByteRope,是基於 Soot 定製化開發,Soot 為我們提供了 CallGraph 的生成能力,但是簡單的 CallGraph 並不能滿足我們對於精準關聯 Activity 的需求,還需進一步的優化改造。

簡單介紹一下呼叫鏈生成的演算法流程:

呼叫鏈生成流程:

  1. 解析 apk,獲得apk 中所有的 class
  2. 解析每個 class,獲得 class 中的所有 method
  3. 解析所有 method,獲得 method 的 body,body 是由一條條命令語句組成,例如複製、方法呼叫等
  4. 解析 method 的 body,一旦出現函式呼叫,就在這個 method 和被呼叫的 method 之間構建一條邊
  5. 當我們遍歷完所有 class 中的所有method's body,那麼呼叫鏈圖也就構建完成了

在構建呼叫鏈圖過程中我們能夠拿到的資訊:

  1. apk 中全部的類
  2. 每個類中的方法
  3. 每個方法的 body
  4. 每個方法所呼叫的其他方法(呼叫邊)

3.2 階段二:構建 Activity-method 呼叫鏈路

3.1.1 獲取 apk 中的所有 Activity

前面提到,呼叫鏈的目的是找到方法所關聯的 Activity,從而推薦自動化測試 case,因此我們需要找到所有的 Activity,將其作為呼叫鏈的入口類。

獲取 Activity 方法

前面提到,在生成呼叫鏈的過程中,我們已經拿到了 apk 中所有類的資訊,只需要遍歷所有類,判斷該類是否繼承於 android.app.Activity、androidx.appcompat.app.AppCompatActivity,如果繼承,則表示該類為 Activity。

3.2.2 生成以 Activity 為入口的呼叫鏈

在階段一中已經生成了全域性呼叫鏈,這裡以 Activity 為入口生成呼叫鏈的目的是確認呼叫鏈中的函式都是由 Activity 出發連結的,從而確保呼叫鏈中的每個方法都關聯了 Activity。

以Activity作為入口生成的呼叫鏈

四、呼叫鏈的優化:關聯 Android 原生元件

前面提到,Soot 為我們提供了 CallGraph 的生成能力,但是簡單的 CallGraph 並不能滿足精準關聯 Activity 的需求,還需進一步的優化改造。

4.1 背景

Android 中很多元件、控制元件是通過佈局檔案或是非同步機制呼叫的,因此即使生成了全域性呼叫鏈,也難以將這些元件、控制元件和所屬的 Activity 關聯起來。

4.1.1 調研

可能會出現這種情況的元件有 Fragment,自定義控制元件。

Fragment

其中 Fragment 一般分為靜態載入動態載入.

  • 靜態載入是在 activity 的佈局檔案中進行載入
  • 動態載入一般是通過 FragmentTransaction.add(fragment).commit() 載入。

自定義控制元件

一般繼承自 View 或 ViewGroup,載入方式也分為靜態載入和動態載入

  • 靜態載入是在 layout 檔案中直接使用控制元件的全限定名作為 Tag
  • 動態載入

一般是通過 ViewGroup.addView() 將自定義控制元件裝載至目 標ViewGroup 中。

4.2 方案

4.2.1 關於顯式呼叫 Fragment、自定義View

面臨的問題:

呼叫鏈不會關聯絡統函式,因此 Fragment、自定義 View 下的 Android 系統 override 方法是不會被關聯到的。

建模:

已有的呼叫鏈和 Fragment 的系統 override 方法關聯起來。

但是由於系統函式本來就沒辦法直接和其他方法進行關聯,因此我們手動新增一條邊,將 caller 方法和 override 方法關聯起來。

caller 方法和 override 方法關聯

4.2.2 關於靜態呼叫佈局檔案中的 Android 元件、Fragment

面臨的問題:

由於通過佈局檔案載入的 Android 元件完全是走Android 系統內部的邏輯,並且是非同步呼叫的方式,因此當前生成的呼叫鏈不存在由 Activity 到這些Android 元件的通路,換句話說,這些元件無法找到它們所關聯的 Activity,從而導致精準測試無法推薦測試用例。

建模

目的:構建從 Activity 到 被靜態呼叫的元件 的通路。思路:尋找靜態呼叫的銜接點,需要找到佈局檔案呼叫Android元件整個流程的所有銜接點,才能夠串聯成呼叫鏈通路:

  1. Activity 通過 setContentView() 設定佈局檔案:

    能夠拿到 Activity 對應的佈局檔案 id

  1. 找到佈局檔案 id 與佈局檔案的對映關係:

    首先,佈局檔案是存放在 apk 中的,我們解壓 apk,就能夠發現佈局檔案存放在 res 目錄下。

    其次,在 apk 中有一個二進位制檔名字叫 resources.arcs,這是apk中所有資源資訊的集合,我們所需要的佈局檔案 id 到佈局檔案的對映關係就存放在 resources.arcs 檔案中。

  2. 解析佈局檔案:

    首先,需要了解 Activiy是如何在佈局檔案中使用 Fragment 以及自定義元件的:

    a.佈局檔案中呼叫 Fragment

    使用 標籤,並在 android:name 屬性中引入 Fragment 類名:

    b.佈局檔案中呼叫自定義 View
    直接呼叫自定義控制元件類作為 layout.xml 的 tag

    c. 佈局檔案中呼叫其他佈局檔案

    i. 通過標籤引入其他佈局

    ii. 通過 android:layout 屬性引入其他佈局

其次,解析佈局檔案,根據上述的 Activity 通過佈局檔案靜態配置元件的方式,設定檔案解析規則,從而獲取 Activity 到 Fragment、自定義 View 的鏈路:

至此,Android 呼叫鏈關聯 Android 原生元件的優化工作已完成。

五、收益

在 5-6 月中,自動化精準測試接入至抖音的 MR 流程,目前已取得了初步的成效:

  1. 抖音 Android: 工具線+基礎業務+社交 的測試人效節省35%
  2. 相比普通自動化任務(Activity 覆蓋率約 0.5%),自動化精準的 Activity 覆蓋率平均提升約15倍,任務平均可以發現約3個 Crash

六、總結與展望

本文首先介紹了自動化精準測試的演變過程,以及我們在實現自動化精準測試過程中遇到了哪些問題,及其解決的方案;其次,本文著重介紹了自動化精準測試流程中,Android 呼叫鏈作用、性質以及它的構建方式,並介紹了 Android 呼叫鏈的優化項,即基於 Android 特性定製化關聯 Activity,使得mr變更方法關聯 Activity 的準確度提升,從而提高測試用例的推薦準確率,減少不必要的測試,提高測試人效。

但是 Android 呼叫鏈的使用場景遠不止於此,它還能夠應用於敏感方法的鏈路追蹤、API  呼叫梳理等場景,但隨之而來的是對呼叫鏈精確程度的要求提升,因此我們對 Android 呼叫鏈的優化做出如下的幾點展望:

  • 完善呼叫鏈演算法,目前呼叫鏈的構建僅侷限於同步呼叫,但在 Android 中存在很多非同步呼叫的邏輯,那麼針對這些非同步呼叫的場景,我們能夠通過建模將其覆蓋,從而提升呼叫鏈的精確度。
  • 生成更細粒度的呼叫鏈,目前呼叫鏈是以方法粒度進行構建的,但是在方法中存在判斷條件導致邏輯分叉,那麼如果能夠將方法基於程式碼語義拆分成 BasicBlock 粒度,並進行呼叫鏈的構建,就能夠使得每條呼叫鏈所表示的邏輯資訊更加精準,從而也能夠在智慧測試領域提升推薦測試 case 的準確度。

關於位元組跳動終端技術團隊

位元組跳動終端技術團隊(Client Infrastructure)是大前端基礎技術的全球化研發團隊(分別在北京、上海、杭州、深圳、廣州、新加坡和美國山景城設有研發團隊),負責整個位元組跳動的大前端基礎設施建設,提升公司全產品線的效能、穩定性和工程效率;支援的產品包括但不限於抖音、今日頭條、西瓜視訊、飛書、懂車帝等,在移動端、Web、Desktop等各終端都有深入研究。

就是現在!客戶端/前端/服務端/端智慧演算法/測試開發 面向全球範圍招聘!一起來用技術改變世界,感興趣請聯絡[email protected],郵件主題 簡歷-姓名-求職意向-期望城市-電話

本文提到的自動化精準測試後續將在火山引擎應用開發套件MARS上線,MARS是位元組跳動終端技術團隊過去九年在抖音、今日頭條、西瓜視訊、飛書、懂車帝等 App 的研發實踐成果,面向移動研發、前端開發、QA、 運維、產品經理、專案經理以及運營角色,提供一站式整體研發解決方案,助力企業研發模式升級,降低企業研發綜合成本。可點選連結進入官網瞭解更多產品資訊。