iOS自動化測試驅動工具探索

位元組跳動終端技術發表於2022-03-03

本文主要介紹了位元組 iOS 自動化測試驅動工具的探索過程及實現原理

作者:位元組跳動終端技術——陳友輝

一、背景

隨著業務的擴張,單個 App 的功能越來越多,工程複雜度越來越高,每天MR可達上百次,程式碼變更可達上千處,航母級的 App 在這一點上更為嚴重。如何在頻繁的程式碼變更中保障App質量,成了各個業務的痛點。靠傳統的人工測試已無法滿足各業務的需求,我們需要將更多的測試場景自動化。

自動化測試需要將人工互動行為變成自動化的原子操作。比如應用安裝解除安裝、螢幕點拖拽及縮放、實體按鍵點選、裝置資訊獲取、應用啟停等等。這就需要一款工具來驅動 iOS 裝置完成以上操作。這篇文章主要介紹位元組 iOS 自動化測試驅動工具 bdc 的探索過程及實現原理。 

二、功能介紹

在介紹 bdc 的探索過程及實現原理之前,先介紹一下 bdc 的能力: 

三、探索歷程

早期方案

在位元組開始大規模建設自動化建設時,Android 已經有較為完善的解決方案,包括其生態自帶的驅動工具 adb 及開源的雲真機管理平臺 STF。但 iOS 在這方面相對滯後,主要是 iOS 缺少一款類似 adb 功能齊全且穩定的驅動工具。

早期 iOS 採用了 Facebook 開源的方案,Facebook 在驅動工具方面先後開源了 wda 與 idb,wda 支援 UI 互動操作,idb 支援應用管理,這在一定程度上滿足了我們的需求,基於這套方案,搭建了第一個版本的自動化測試機架。

早期的機架也很簡單,機器的規模也不大

 

經過一段時間的實踐,我們遇到了以下幾個問題

  • wda 部分介面執行耗時較長,效率低下,無法滿足高頻率排程的需求
  • idb 很多命令只支援模擬器,對真機不夠友好,無法滿足我們的功能擴充套件
  • 命令執行失敗率高,工具穩定性差,且出問題後難以排查
  • 整套流程強依賴 Xcode 環境,規模化、自動化部署成本高,無法應對上千臺手機的部署

工具改良

UI 互動改造

為了解決上述問題,我們結合 wda 的實現思路,實現了一個更高效穩定的 XCTest 工具。我們對 XCTest 相關的介面進行了review,並找到了XCTest 實現跨程式呼叫最底層的介面。通過這些介面,可以直接呼叫 testmanagerd 程式。隨後基於這些底層介面封裝了一套新的介面,可以實現螢幕的點拖拽、實體按鍵點選、文字輸入等操作。相比 wda,在執行速度和穩定性上獲得大幅提升。

testmanagerd 程式是一個開發者守護程式,在 iOS 裝置開啟開發者模式後,testmanagerd 程式的映象會被掛載到 iOS 裝置系統的 Developer 目錄,並被 launchd 程式啟動。XCTest 使用蘋果自帶的 XPC 機制與 testmanagerd 進行通訊,利用 NSXPCConnection,只需知道服務的 id 即可建立通訊,testmanagerd 服務的 id 可在其映象檔案裡找到。映象的路徑位於:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport

解壓映象後,可在 Library/LaunchDeamons 目錄下找到名為 com.apple.testmanagerd 的 plist 檔案,開啟後可以看到其 id 為 com.apple.testmanagerd(iOS14 後 id 有所變動)

裝置互動工具

搞定 UI 互動後,接下來就需要找到能完美支援裝置管理、應用管理、沙盒檔案管理的方案。一開始我們也是想基於 idb 進行優化,但隨後發現 idb 內部使用了大量的 api,這些 api 絕大部分缺少文件,優化成本較高,且這些私有 api 會隨著 Xcode 版本的變動而更新,維護會很麻煩。所以放棄了 idb 轉而尋找其他替代方案。

這時我們發現了另一個開源實現 libimobiledevice,libimobiledevice 支援通過 USB 的方式與 iOS 裝置進行通訊,且支援應用安裝解除安裝、裝置資訊獲取、沙盒檔案操作等功能。libimobiledevice 在使用體驗上,操作簡單,功能穩定。但缺點是功能有限,不能完全符合我們的訴求,接下來我們對 libimobiledevice 的實現原理進行了探究。

基於 USB 與 iOS 裝置通訊

蘋果自身有一些 Mac App 需要通過 USB 跟 iOS 裝置進行通訊,比如 iTunes、XCode 及其套件等等。雙端通訊需要基於一定的協議,通過USB通訊需要使用USB協議,但USB協議具有一定的侷限性,直接使用成本較高。所以蘋果在USB協議的基礎上支援了TCP通訊的能力,以此減小使用成本。

蘋果通過 usbmuxd 來提供基於 USB 實現 TCP 通訊的能力。usbmuxd是一個守護程式,它在 USB協議上實現了多路 TCP 連線,可以讓應用層無感知的基於 USB 通道進行 TCP 通訊。

macOS 上的 usbmuxd 配置檔案位於/Library/Apple/System/Library/LaunchDaemons,開啟後如下: 

usbmuxd 的配置檔案記錄了載入屬性、服務名稱、可執行檔案路徑、socket 屬性等資訊。從上面的配置檔案可以看到,usbmuxd 建立了一個 Unix 域的 socket。這個 socket 主要用於跟上層應用建立連線,實現跨程式通訊。基於 usbmuxd 進行網路通訊的流程如下: 

發現iOS裝置系統服務並完成呼叫

從上述 usbmuxd 的通訊流程可知,想要跟iOS裝置中的服務進行通訊,只需要知道具體服務的埠即可。那麼服務埠該如何獲取?一般有兩種方式,一種是直接hardcode,另一種是動態獲取。iOS裝置內部的服務眾多,如果全部 hardcode,其維護成本較高,且安全性及穩定性較低。如果是動態獲取,那麼獲取的方式將成為另一個問題,因為動態獲取本身也需要通訊。

蘋果則採用了兩者結合的方式。蘋果在iOS系統內部增加了一個 lockdownd 的守護程式,這個守護程式以 root 特權執行,具有訪問 iOS 系統資訊的能力,且執行在固定的埠。lockdownd 的配置檔案如下,其路徑位於 iOS 系統的/System/Library/LaunchDaemons/目錄。

由此可以看出,lockdownd 分別支援 Unix 域的 socket 與非 Unix 域的 socket,對於非 Unix 域的socket,其監聽的埠固定在62078。

當 PC 端想要跟 iOS 裝置中的某個服務進行通訊時,先通過 lockdownd 查詢對應服務的埠,然後再跟對應的服務建立 socket 連線,其流程如下: 

libimobiledevice 就是利用以上流程對 iOS 裝置中的服務進行了呼叫,比如在操作沙盒檔案時,就是呼叫了 iOS 裝置中的 afc、house_arrest 服務。

服務型別

搞清楚了呼叫流程,那麼 iOS 裝置中都有哪些服務呢?iOS 系統服務主要分為兩類:一類是debug相關服務,開啟開發者選項後才具備相應服務。另一類是非 debug 服務,預設都具備。

debug 相關服務的配置檔案及可執行檔案都被打包放在了 Xcode 中,位於/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/目錄,當 iOS 裝置連線 Xcode 時,Xcode 會自動將此目錄下對應的DeveloperDiskImage掛載到 iOS 系統中。其包含的服務如下:

上述提到的 testmanagerd 就位於其中。

非debug服務位於iOS系統的/System/Library/LaunchDaemons目錄。通過越獄後,我們可以檢視/System/Library/LaunchDaemons目錄下的內容,以下是部分服務的截圖,可以看到,服務非常之多。 

能力擴充套件

搞清楚 libimobiledevice 實現原理後,基於這個思路,我們又自行探索了其他一些服務的能力,這其中就包括 Xcode 相關的服務。Xcode 為開發者提供了專業、穩定的工具集,而 Xcode 的工具集也是利用以上機制與 iOS 裝置進行通訊。基於Xcode 的能力,我們實現了 Trace 採集、裝置應用管理等功能。

支援 Linux

工具的問題解決了,接下來就是部署的問題。我們需要面對上千臺 iOS 裝置接入及每天上萬次的服務排程,且裝置之間的環境需要相互隔離,互不干擾。所以在裝置及工具的部署上需要簡單、高效。比較好的解決方案是採用 docker 部署,每臺裝置及其對應的驅動工具都用 docker 分離開,這樣能做到環境隔離,且部署簡單。但 Mac 對 docker 的支援並不那麼友好,且我們的工具本身依賴 Xcode 環境。

既然 Mac 對 docker 的支援不友好,那我們是否能擺脫對 Mac 的依賴,將裝置及工具部署在 Linux 上。順著這個思路,我們開始了對工具的第二次改造。

支援 XCTest 啟動

工具對 Mac 的依賴主要來源於 XCTest 工具的啟動,類似於 wda。我們將 XCTest 的介面封裝在了一個普通的 App 裡,然後在這個普通的 App 裡搭建一個 websocket server,這樣就可以通過網路與這個 App 通訊,實現 XCTest API 的呼叫。但經過嘗試後,發現普通的 App 呼叫 XCTest API 並不會產生預期的效果。所以還需要一些特殊的操作才能使普通App具備呼叫 XCTest API 的能力。

我們從正常的 XCTest-Runner 入手探索其啟動流程。以home鍵的點選方法為切入點,通過 LLDB 追蹤testmanagerd 程式中介面的呼叫流程,發現瞭如下關鍵的介面:

_IDE_authorizeTestSessionWithProcessID:

分析後發現,testmanagerd 會維護一個程式白名單,只有將 App 的程式 ID 加到這個白名單裡,這個App 才具備呼叫 XCTest API 的能力。而上述註冊白名單的介面則可以通過前面提到的 lockdownd 的方式呼叫。基於此,我們實現了擺脫對 Mac 環境的依賴,在 Linux 實現了 docker 化部署。

 

四、執行效果

bdc 工具上線已有一年多。目前支撐了公司自動化測試平臺上千臺 iOS 裝置每日上萬次的裝置排程及測試任務執行。自動化測試平臺涵蓋了穩定性、UI、效能、單元測試等多項測試能力,服務公司上百個業務。

裝置機架從原有的簡陋裝置已升級為全球機房,服務於全球業務。

 


? 火山引擎 APMPlus 應用效能監控是火山引擎應用開發套件 MARS 下的效能監控產品。我們通過先進的資料採集與監控技術,為企業提供全鏈路的應用效能監控服務,助力企業提升異常問題排查與解決的效率。目前我們面向中小企業特別推出「APMPlus 應用效能監控企業助力行動」,為中小企業提供應用效能監控免費資源包。現在申請,有機會獲得60天免費效能監控服務,最高可享6000萬條事件量。

? 點選這裡,立即申請

相關文章