一個 GitHub Actions matrix 設定可以展開成多個獨立 Job,但每個 Job 必須先取得可用 Runner 才能真正執行;這是 GitHub Actions 工作原理文件所描述的基本模型。

症狀: matrix 展開了,任務卻長時間停在佇列,或多個 Simulator 互相覆蓋狀態。
最快解法:先用 Xcode Test Plans 或 only-testing 按穩定依賴邊界拆分,再把每個分片路由到不同的遠端 Mac;只有一台 Runner 時,不要把 Job 數量當成實際並行容量。

這篇適合三類人:

  • iOS 測試工程師:需要把不斷增長的 XCTest UI 套件拆成可獨立執行、可定位失敗的分片。
  • DevOps 工程師:需要處理 GitHub Actions matrix、Runner 標籤、並發控制與測試產物彙整。
  • 研發負責人:需要根據佇列、穩定性和節點利用率,判斷是否增加遠端 Mac,而不是盲目擴大矩陣。
01

基線建立:先量清楚測試真正慢在哪裡

不要先改 workflow。先用目前的完整 UI 測試套件跑出一份基線,並記錄以下資料:

  1. 測試套件的實際執行順序。
  2. 每個測試或群組的大致耗時分布。
  3. 失敗發生在建置、啟動 Simulator、測試步驟還是結果上傳。
  4. Job 等待 Runner 的時間。
  5. 產生的 .xcresult、截圖、主控台記錄與測試清單。

這一步要把三種並行拆開看:

  • GitHub Actions Job 並行:多個 matrix Job 是否同時取得 Runner。
  • 多台遠端 Mac 並行:不同節點是否同時執行不同分片。
  • 單台 Mac 內部並行:Xcode 或 Simulator 是否在同一主機上同時執行測試。

三者不是同一個擴容指標。你增加 matrix 組合,只會增加等待中的工作;你增加單機內部並行,也不代表每個 Simulator 都能獲得獨立的 CPU、記憶體和磁碟 I/O。

Apple 的執行測試與解讀結果文件可用來核對測試結果與失敗資訊。實作時,還應在目標節點執行 xcodebuild -help,確認當前 Xcode 接受的參數,不要直接複製另一個版本的指令。

基線觀察 代表的瓶頸 下一步
Runner 長時間沒有接單 可用節點不足或標籤不匹配 檢查 Runner 狀態、Group 與標籤
Job 已啟動但 Simulator 很慢 單機 CPU、記憶體或 I/O 受壓 降低單機並行,先做隔離試跑
某些測試單獨也會失敗 測試程式或測試資料問題 先修復測試,不用增加節點
完整套件只在並行時失敗 共享狀態或資源碰撞 分離帳戶、路徑、Port 和 destination
02

首次分片:依賴邊界比檔案數更重要

第一次拆分不要按測試檔案數平均切割。檔案數相同,不代表執行時間相同;更不能把一條必須連續完成的使用者流程拆到不同 Job。

優先使用以下邊界:

  • Xcode Test Plans 中的具名測試計畫。
  • 測試 Target 或既有 Suite。
  • only-testing 指定的測試類別與方法。
  • 已經能獨立準備資料、啟動 App 和清理狀態的功能群組。

以下狀態應先保留在同一分片:

  • 登入狀態依賴前一個測試留下的 Session。
  • 多個測試共用同一個測試帳戶或推送環境。
  • 購買、付款、權限變更等連續業務流程。
  • 會修改共用後端資料、檔案或測試開關的測試。

Apple 的組織測試以改善回饋文件可作為測試分組的官方參考。你的分片命名也要固定,例如使用 ui-authui-checkoutui-profile 這類佔位識別,不要用「第一批」「第二批」等難以維護的名稱。

每個分片都應有四份資料:

  • 測試範圍:包含哪些類別或方法。
  • 輸入條件:裝置、帳戶、後端環境和必要 Fixture。
  • 預期責任:這個分片驗證哪一段產品行為。
  • 單獨入口:開發者可以從本機或遠端 Mac 直接重現。

第一次拆分完成後,先跑完整套件,再跑所有分片。比較測試清單,確認沒有漏測、重複或因 Test Plan 設定而被排除的案例。

03

Matrix 接入:讓 Job 找到正確的遠端 Mac

分片邊界穩定後,才把它們映射到 GitHub Actions matrix。每個 matrix 組合應包含至少一個固定的分片識別,並由 workflow 將該識別傳給測試命令。

自託管 Runner 的路由不是靠名稱猜測。請使用GitHub 自託管 Runner 標籤文件核對標籤與 Runner Group 的匹配規則,確保每台遠端 Mac 具備正確的:

  • Xcode 與 macOS 環境。
  • Simulator runtime 與裝置型號。
  • 專案憑證、Keychain 和檔案權限。
  • 網路連線與測試後端存取權。
  • 必要的 Shell、Homebrew 套件和快取目錄。

要同時檢查三個控制層:

  1. max-parallel 限制 workflow 同時啟動的矩陣任務數。
  2. concurrency 控制同一分支或同一環境是否互相取消、等待。
  3. 實際可用 Runner 數量決定任務能否真正離開佇列。

GitHub 的工作流程語法文件可用來核對 matrix、並發與條件設定。這裡不要把 max-parallel 寫成「Mac 容量」。它只是工作流程層的上限;如果只有一台符合標籤的 Runner,其他 Job 仍然會等待。

建置路徑通常有兩種。每個分片重新建置,流程簡單但會重複消耗時間;先執行 build-for-testing,再把測試產物分發到各節點,可能降低重複建置,但要處理產物版本、簽名、路徑和上傳失敗。對小型套件,優先選可重現性較高的方案;對建置成本已成為主瓶頸的專案,才評估產物分發。

04

首次並行:先隔離 Simulator 和檔案狀態

矩陣第一次啟動時,不要直接把所有分片推到最高並行。先選兩個相對獨立的分片,觀察它們是否在同一台或不同遠端 Mac 上取得正確資源。

每個 Job 至少要有獨立的:

  • Simulator destination 或可識別的裝置名稱。
  • DerivedData 目錄。
  • .xcresult 輸出路徑。
  • 暫存目錄與測試檔案。
  • Port、測試帳戶與後端資料範圍。

如果兩個 Job 共用同一個結果路徑,後完成的任務可能覆蓋先完成的結果。這不是「偶發的 GitHub 問題」,而是輸出設計錯誤。

提醒:單台遠端 Mac 內部開啟 Xcode 並行測試,與多台 Mac 執行 matrix 分片,必須分開驗證。先改一個並行層級,再看 CPU、記憶體、磁碟 I/O、Simulator 記錄和失敗截圖,否則無法知道是哪一層造成回歸。

決策條件

  • 若兩個分片可獨立執行,且不同 Job 能取得不同 Runner,選擇跨節點 matrix。
  • 若只有一台 Runner,但測試已完全隔離,且資源觀察沒有明顯壓力,才試驗單機內部並行。
  • 若測試共享帳戶、Session 或後端資料,回退到同一分片內串行執行。
  • 若主要耗時是 Runner 佇列,評估增加遠端 Mac;不要繼續增加 matrix 組合。
  • 若主要耗時是單一超長分片,先重組測試邊界,再談增加節點。
  • 若並行後失敗率上升且無法重現,回退至串行基線,保留失敗產物後再拆解共享狀態。
05

結果彙整:把「紅燈」分成三種問題

每個分片都要獨立上傳:

  • .xcresult 測試結果包。
  • 主控台與 xcodebuild 記錄。
  • 失敗截圖或影片。
  • 分片識別與實際執行清單。
  • 節點、Simulator destination 和提交版本。

Apple 的測試結果文件可用來核對結果包中的測試狀態。彙整 Job 不應只檢查上一個 Job 是否成功,而要依次檢查:

  1. 是否每個預期分片都已完成。
  2. 是否有分片因 Runner、簽名、Simulator 或網路而未執行。
  3. 是否有真實測試斷言失敗。
  4. 是否有測試只在並行時失敗。
  5. 是否有重試成功掩蓋第一次失敗。

重試可以用來判斷波動,但不能把第二次成功直接取代第一次失敗。建議保留 first_attemptretry_attempt 兩組狀態,並為不穩定測試建立隔離清單與負責人。

覆蓋率也不能把多個百分比直接相加。你需要先確認不同分片是否使用相同原始碼、是否能安全合併執行資料,以及採用哪個 Apple 官方工具驗證合併後結果。若無法證明合併正確,應分別展示各分片覆蓋範圍,不要產生看似精確但不可驗證的總數。

06

常見問題

這一階段最容易出現的誤判,是把 workflow 已建立、Job 已排隊和測試已在多台 Mac 執行,當成同一件事。以下四個判斷可用來快速核對實作方向。

07

上線驗收:用真實 Pull Request 決定長期架構

完成兩分片試跑後,不要立即宣稱並行成功。讓流程通過一段真實 Pull Request,觀察以下項目:

  • 端到端回饋時間是否縮短。
  • Runner 佇列是否成為主要耗時。
  • 最長分片是否長期主導總時間。
  • 分片之間的耗時是否嚴重失衡。
  • 失敗能否用同一提交和同一資料重現。
  • 遠端 Mac 是否長時間滿載或頻繁閒置。
  • .xcresult 與截圖是否每次都能取得。

你可以把結果分成三種長期方案:

觀察結果 建議方案 需要保留的安全措施
測試依賴多,並行收益不穩定 單機串行或少量分片 發布前全量串行基線
分片獨立,節點佇列明顯 多台遠端 Mac 的 matrix Runner 標籤與結果彙整
分片獨立但單機資源足夠 單機內部並行加低數量 Job 獨立 Simulator 與回滾入口
並行後結果不一致 雙軌執行 先修復共享狀態,再擴大並行

發布關鍵測試不必一開始就完全並行。可以保留一條串行或定期全量 workflow,與快速分片流程互相校對。當兩者的測試清單、失敗分類和結果產物都穩定後,再決定是否把並行流程作為主要閘門。

如果你正在規劃實際節點,可先閱讀遠端 Mac 的 Xcode 與 Simulator 驗收方案,把 Xcode、Simulator、簽名和權限列入驗收,而不是只看「Runner online」。若團隊需要比較不同地區的使用條件,也可參考香港 Mac 遠端方案中的連線與交付資訊。

08

遠端 Mac 的方案邊界

如果你的目前方案是單台本地 Mac 或單一固定 Runner,常見缺點是:UI 測試會與開發者工作互相爭用資源;長時間測試容易佔住發布前的唯一節點;故障時沒有獨立環境可以重現;臨時增加測試分片也只會讓 Job 堆在佇列。若你改用一般 Linux 雲端主機,則無法直接提供 Xcode、macOS Simulator 和 Apple 專屬工具鏈。

因此,當你已用本文流程完成單節點基線和兩分片試跑,並確認瓶頸確實是 Runner 不足,而不是共享狀態或 Simulator 故障時,租用 KVMNODE 的獨立遠端 Mac 會比臨時添購硬體更容易驗證。你可以按測試週期取得 macOS 環境,保留 root 權限,透過 SSH、VNC 或網頁控制台接入,再把 Runner 標籤、節點隔離和結果保存納入既有 GitHub Actions 流程。

若專案是長期高負載、需要實體 USB 或必須完全掌控硬體生命週期,自購 Mac 仍可能更合適;若只是短期擴充 UI 測試容量、驗證新分片策略或應付發布高峰,遠端 Mac 通常更符合「先驗證、再擴容」的工程決策。