DevOps / CI·CD 2026年4月23日

2026-04-23 租用雲端 Mac 上的 iOS 測試計畫、並行 XCTest 與 xcresult 排障

MacXCode 技術團隊 2026年4月23日 約 18 分鐘閱讀

HK / JP / KR / SG / US 租用 Apple Silicon Mac 執行 xcodebuild testiOS QA 負責人CI 維護者,很快會被臨時 -only-testing 參數淹沒。本文撰於 2026-04-23,把測試計畫固化為版控意圖,說明如何依 destination 並行化,以及如何以穩定命名把 xcresult 送到物件儲存——並與 無頭模擬器測試DerivedData 與 xcresult 隔離並行 xcodebuild 任務扇出 互補閱讀。

遠端 CI 與本機筆電最大的差別在於可觀測性與清理紀律:沒有人在旁邊按「Clean Build Folder」。因此測試計畫不僅服務開發者體驗,更是值班同仁判斷「這次到底跑了哪些組態」的唯一可信來源。

當團隊從「單台 Mac 偶爾跑測試」演進到「多區域多租戶共享建置農場」時,最常見的返工來源是命令列參數與 YAML 註解不同步:工程師在 Slack 裡口耳相傳的 -only-testing 組合,三天後就會與主線分叉。把選擇邏輯收進 .xctestplan,等於為管線買了一張可稽核的底片——無論是合規抽查還是事故檢討,都能用 Git 歷史回答「當時到底執行了哪條路徑」。

為什麼遠端 CI 需要測試計畫

*.xctestplan 是把意圖寫進 Git 的版控工件:預設測試 target、選項,以及環境變數矩陣都能在 PR 裡被審閱。相比把巨型 shell 字串塞進 CI YAML,當產品經理在點發布後追問「哪些組態真的跑過」時,你能直接指向計畫檔。請讓 scheme 保持單一且穩定,把 Debug/Release、Address Sanitizer 切換,或暫時停用不穩定 UI 套件等變化放在計畫層,而不是刪測試程式碼。

在 SSH 無顯示器主機上,測試計畫還能減少「複製貼上命令」的漂移:每個分片只變少量參數(分片索引、結果路徑),核心選擇邏輯仍在儲存庫裡。

測試計畫 vs 只用 scheme:決策表

做法 適用情境 SSH 可維運性
提交 .xctestplan + -testPlan Name 筆電與建置機可重現;PR 可 diff 高——每分片一條命令
scheme 預設 + -only-testing 突擊驗證與熱修泳道 中——YAML 字串膨脹
多個 scheme 拆分測試 target 彼此無關應用共存的單體儲存庫 低——scheme 維護重複

xcodebuild 測試呼叫範本

DEVELOPER_DIR 固定到你在該主機上驗證過的 Xcode,再為每次作業明確指定結果套件路徑與 DerivedData。把 job id 寫進路徑,便於與 自架 Runner 或內部排程器關聯。

DEVELOPER_DIR=/Applications/Xcode.app xcodebuild test -workspace App.xcworkspace -scheme App -testPlan Nightly -destination 'platform=iOS Simulator,name=iPhone 16' -resultBundlePath "$CI_RESULTS/run-${CI_SHARD_ID}.xcresult" -derivedDataPath "$CI_DERIVED_DATA/${CI_SHARD_ID}"

建議公開的指標: 對已知不穩定案例預設 3 次本機重試並設硬上限;單套件 p95 超過 18 分鐘視為擴容訊號;在 12 核 M4 級主機上,2–4 個並行的 xcodebuild test 行程通常是甜區,再往上往往被 I/O 爭用吞噬牆鐘收益。

並行泳道、分片與佇列

兩條正交軸線:(a) 單主機多模擬器;(b) 多個子作業各持一個 destination。(a) 省成本但容易讓 CoreSimulator 與磁碟 I/O 進入非線性變慢。(b) 更接近新加坡美國東部嚴肅團隊的扇出方式:每片自有 CORESIMULATOR_HOST 前綴與 DerivedData,並各自上傳 *.xcresult。佇列紀律可複用並行 xcodebuild 長文,但測試要額外設重試預算依分片逾時——通常比封存作業寬 20–35%,因為 XCTest 啟動變異確實存在。

分片歸屬、命名與重跑

給每個子作業一個穩定CI_SHARD_ID,不要依賴分支名稱——帶斜線與 emoji 的分支會弄壞樸素的 shell glob。把序號總數編碼進 ID,值班讀記錄不必開啟 YAML:例如 test-3-of-12 優於裸 shard-uuid。當有人點「僅重跑失敗測試」時,讓編排層把失敗 bundle id 映射回 -only-testing: 清單,同時保持相同的 DEVELOPER_DIRresultBundlePath 家族——避免第二個幾乎同名的 xcresult 在儲存桶裡撞車。若合併門檻要求「全綠」,請文件化重跑政策是否允許替換分片標籤;此處的模糊會把不穩定案例升級成流程事故

單元測試幾分鐘結束而UI 測試拖到午後的混合管線,不要讓慢分片擋住快回饋:先把部分 xcresult 上傳到暫存前綴,全部通過後再提升到「發布」前綴——儀表板仍可從半成品套件讀取牆鐘進度,卻不會把未完成執行與綠色主線混為一談。此模式與 DerivedData 隔離文章天然契合:清理時每個分片可獨立刪除自己的 DerivedData 根目錄,而同碟上的兄弟分片不受影響。

xcresult 套件、合併策略與留存

每個 xcresult 都是自包含測試報告——優先每分片一個套件,而不是依賴 Xcode 跨版本未保證的磁碟級「合併」。下游許多團隊在 CI 裡匯出 JUnit 或 JSON 餵給品質儀表板,同時封存原始 xcresult 供互動式排查。留存策略可與 dSYM 對齊:參見 dSYM 與當機符號化,當測試與建置共用主機時一併規劃。

磁碟衛生: CoreSimulator 在混合分片跑一週後可能洩漏數 GB 陳舊裝置——與 隔離清理 的例行作業同一視窗執行,避免「報表上空間還夠,凌晨卻告警」的錯覺。

裸金屬上 XCTest 不穩定矩陣

模式 較可能的根因 第一步動作
六分之一機率失敗,測試碰觸 UI 與動畫 逾時 vs 模擬器彩現變異 穩定動畫;提高逾時並在成品中附帶錄影
整組在某個分片全掛 分片環境不一致、缺資料種子 斷言環境對齊;唯讀掛載共用 fixture
週一後所有分片變慢 系統修補程式 + 執行時變更 重標 p95;明確釘選 Xcode/執行時版本

NVMe、1–2 TB 選項與「為何不能無限 CI」

測試泳道比純封存更快堆出瞬時 I/O。在租用的 1 TB 或 2 TB Mac 上,建議每週掃除:綠色分支的 xcresult 保留 7 天,預設分支 30 天,與 App Store Connect 版本對應的 tag 90 天——合規可收緊。若簽章不穩定與測試不穩定交織,先對照 鑰匙圈與描述檔,避免誤讀 XCTest 輸出。

另一個容易被忽略的維度是溫度與散熱裕量:並行 UI 測試會把 CPU 與 GPU 同時頂在高位,NVMe 也會長時間處於高佇列深度。裸金屬 Mac mini 相比超賣虛擬機更能保持可重現的溫控曲線,因而使「相同的分片拓樸」在週二與週六得到接近的 p95。若你發現週五晚高峰牆鐘系統性變差,先檢查是否有人把封存與測試疊在同一維護視窗,再對照磁碟剩餘空間與模擬器殘留。

把本手冊與 模擬器測試基礎說明中心(存取模式)以及需要拆分測試/封存機隊時的 節點選型 連在一起用。

無頭環境中的測試計畫 FAQ

問題 實務答案
偵錯測試一定要開 VNC 嗎? 自動化很少需要——優先用成品;UI 難題再用 VNC 破窗。
每台 Mac 多少分片? UI 測試可從 CPU 核心數 / 3 起步;p95 穩定一週後再加。
單元與 UI 能放同一計畫嗎? 小應用可以;大儲存庫依層次拆計畫以縮短關鍵路徑。

為何 XCTest 重的 CI 仍適合 Mac mini M4 裸金屬

Mac mini M4 在 MacXCode 上提供可預期的 CoreSimulator 表現與不經過嘈雜虛擬化層的 NVMe——這正是 XCTest p95 指標最在意的表面。藉由 HK · JP · KR · SG · US 區域,你可以把測試機構與封存主機同地部署且保持相同 SSH 工作流,按需加 1–2 TB 承載成品密集型組織,並在重試預算證明你確實被 I/O 或 CPU 卡住(而非計畫設定錯誤)時,從 定價 彈性加機。

為測試負載擴增雲端 Mac

M4 · NVMe · 多區域