雲端 Mac 的 Xcode 測試計畫一致性門檻

雲端 Mac 的 Xcode 測試計畫一致性門檻

團隊將單元測試、介面測試與 UI 測試拆分到多個 Xcode Test Plan 後,最常見的問題往往不是測試失敗,而是測試根本沒有依照預期執行:有人在本機 Scheme 中切換了預設計畫,有人暫時略過某個測試案例後便提交檔案,也有人修改了環境變數,卻未同步更新流水線。雲端 Mac 會如實執行儲存庫中的設定,因此第一步不是增加重試次數,而是將測試計畫本身視為需要審查的程式碼。

先固定唯一且可追蹤的入口

Scheme 必須是共享檔案,路徑通常為 App.xcodeproj/xcshareddata/xcschemes/App-CI.xcscheme。不要依賴使用者目錄下的 xcuserdata,其中的設定無法穩定納入版本控制。

在 Scheme 的 Test Action 中引用儲存庫內的 App-CI.xctestplan,再讓流水線明確傳入計畫名稱。建置節點開始工作時,先檢查該計畫是否可見:

set -euo pipefail

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App-CI \
  -showTestPlans

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App-CI \
  -testPlan App-CI \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  test

-testPlan 不可省略。否則一旦 Scheme 的預設值發生變化,命令仍可能成功,但實際執行的測試集合可能已經不同。

將計畫檔案拆解成可核對的契約

.xctestplan 是 JSON 檔案,很適合進行靜態檢查。門檻至少應涵蓋四類欄位:

檢查項目 應有規則 失敗代表的意義
configurations 必須包含 CI 設定 流水線入口遭到刪除或重新命名
testTargets 必須包含約定的目標 某組測試未被執行
skippedTests 只能符合允許清單 新增了未經說明的略過項目
environmentVariableEntries 禁止敏感值,且關鍵鍵名必須存在 環境發生漂移,或資訊進入儲存庫

不要直接比較整份 JSON 的文字順序。Xcode 可能重新排列陣列或補充欄位,逐字比較只會產生沒有意義的失敗。應解析其結構,僅驗證團隊真正依賴的約束。

測試計畫門檻的目的並非阻止任何修改,而是讓「少執行了哪些測試、變更了哪些環境設定」在合併前清楚可見。

使用指令碼攔截目標遺漏與略過項目

以下指令碼會檢查 CI 設定、必要的測試目標,以及未登記的略過項目。允許清單應保持精簡,並要求在程式碼審查中說明移除條件。

#!/usr/bin/env python3
import json
import sys
from pathlib import Path

plan = json.loads(Path("App-CI.xctestplan").read_text())
required_targets = {"AppTests", "AppIntegrationTests"}
allowed_skips = {
    "AppIntegrationTests/testTemporaryServerResponse"
}

config_names = {item["name"] for item in plan.get("configurations", [])}
if "CI" not in config_names:
    sys.exit("Missing CI test configuration")

targets = plan.get("testTargets", [])
target_names = {
    item.get("target", {}).get("name")
    for item in targets
}
missing = required_targets - target_names
if missing:
    sys.exit(f"Missing test targets: {sorted(missing)}")

actual_skips = {
    test
    for item in targets
    for test in item.get("skippedTests", [])
}
unexpected = actual_skips - allowed_skips
if unexpected:
    sys.exit(f"Unapproved skipped tests: {sorted(unexpected)}")

請在測試命令之前執行此指令碼。若檢查失敗,不應自動改寫計畫檔案,因為自動修正可能掩蓋開發者刻意進行的設定變更。

為允許清單設定退出條件

允許清單不能變成永久的垃圾桶。每個項目至少都應對應一筆缺陷記錄、負責人及刪除條件。若團隊不想再維護另一份結構化檔案,可以在程式碼審查範本中強制填寫這些資訊,並定期執行指令碼輸出目前的清單。

分離穩定變數與流水線機密

測試計畫適合保存不會隨節點變動的開關,例如 UITEST_MODE=1、固定語言或模擬服務模式。存取權杖、私密金鑰與一次性憑證不應寫入計畫檔案,也不應以明文參數形式放入 Scheme。

雲端 Mac 的流水線可以在執行前注入機密,並讓測試程序讀取環境變數。審計指令碼則只驗證鍵名是否存在,以及值是否來自允許的固定集合。如此既能確保計畫可重現,也不會讓敏感資訊混入儲存庫。

還要留意變數展開所指向的目標。如果計畫引用了已重新命名的 Target,Xcode 介面或許仍可開啟檔案,但執行階段的變數解析可能偏離預期。重新命名專案時,應將計畫檢查與 xcodebuild -list 一併執行。

讓失敗保留足夠的證據

通過門檻後再執行完整測試,並將結果套件寫入固定目錄:

rm -rf artifacts/App-CI.xcresult

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App-CI \
  -testPlan App-CI \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -resultBundlePath artifacts/App-CI.xcresult \
  test

失敗時應保留三項資訊:目前的 .xctestplan、實際執行的命令,以及 .xcresult。只保存主控台末尾的日誌,不足以判斷究竟是測試邏輯失敗、目標未載入,還是計畫設定發生變化。

在 ZoneMini 上執行固定測試節點時,也應在每次工作開始前重新執行靜態審計,而不是假設長期運作的環境必然一致。共享 Scheme 確定入口,測試計畫描述集合,審計指令碼約束變更,結果套件保存證據;唯有四層同時存在,「測試通過」才會具有穩定且一致的意義。

常見問題

為什麼不能只在 CI 固定一條 xcodebuild 指令?

指令只能指定 Scheme 與測試計畫名稱,無法防止計畫檔內的測試目標、略過項目及環境變數被改動,因此還要稽核 .xctestplan 與共享 Scheme。

測試計畫內的 skippedTests 是否都該禁止?

不需要全面禁止。確有需要的暫時略過項目應放入包含負責人、原因及截止條件的允許清單,未登記的新項目則阻擋合併。

如何避免開發機與雲端 Mac 選到不同測試計畫?

把 Scheme 存入 xcshareddata/xcschemes,明確引用版本庫中的 .xctestplan,並在 CI 固定 -testPlan 參數,再用 -showTestPlans 驗證。

專用建置時段

為持續建置工作選擇獨享雲端 Mac

確認 M4、記憶體、儲存空間、節點與計費週期,再將固定建置環境接入現有工作流程。

選擇配置並訂購