同一個提交在開發者電腦上跑完所有測試,進入雲端 Mac 後卻只執行其中一部分,最常見的原因通常不是測試程式碼,而是 .xctestplan 已在不知不覺間發生漂移:有人在 Xcode 裡暫時略過測試案例、關閉診斷項目,或將只適用於本機的環境變數存進計畫檔。解決方式是將 XCTestPlan 視為建置輸入,而不是圖形介面的附屬設定。
先固定測試契約的邊界
可重現的測試進入點至少應包含共享 Scheme、XCTestPlan、執行目標與命令列參數。首先確認 Scheme 與計畫確實能由命令列找到:
WORKSPACE="${WORKSPACE:-App.xcworkspace}"
SCHEME="${SCHEME:-App}"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-showTestPlans
如果輸出中沒有預期的計畫,請先檢查 Scheme 是否設為 Shared、計畫檔是否已加入儲存庫,以及 Scheme 的 Test 動作是否有引用該計畫。不要用 CI 參數臨時補上一個不存在的計畫,否則本機進入點與流水線只會繼續分歧。
計畫中應明確審查以下內容:
| 項目 | 需要確認的事實 | 常見漂移 |
|---|---|---|
| Test Targets | 哪些單元測試與 UI 測試會參與執行 | 新目標未加入 |
| Selected Tests | 是否只執行指定測試案例 | 除錯時的選擇被提交 |
| Skipped Tests | 每個略過項目是否都有負責人與期限 | 失敗案例遭永久隱藏 |
| Configurations | 語言、地區與啟動參數 | 本機設定覆蓋預設設定 |
| Diagnostics | 當機、執行緒與效能診斷策略 | 為了加速而意外關閉 |
| Parallelization | 哪些目標允許平行執行 | 共用狀態的測試互相干擾 |
略過測試並不等於修復。任何新增的 skipped test 都應像程式碼變更一樣接受審查,並說明恢復條件。
產生易讀的正規化差異
XCTestPlan 是結構化檔案,直接審查原始文字時,很容易受到欄位順序與自動產生識別碼的干擾。可以在儲存庫中保存一份正規化基準,只移除不影響執行語意的設定識別碼:
PLAN="App.xctestplan"
CURRENT=".ci/xctestplan.current.json"
BASELINE=".ci/xctestplan.baseline.json"
mkdir -p .ci
plutil -convert json -o - "$PLAN" |
jq -S 'del(.configurations[]?.id)' > "$CURRENT"
diff -u "$BASELINE" "$CURRENT"
首次導入時,請先人工核對 $CURRENT,再將它複製為基準並提交。後續流水線只需產生目前檔案並執行 diff。目標、略過項目、環境變數、參數與診斷選項都必須保留;不要為了得到「乾淨的差異」而繼續刪除業務欄位。
正規化指令碼同樣屬於測試基礎設施。指令碼變更與計畫變更應放在同一個合併請求中呈現,否則只要放寬一次過濾規則,後續所有漂移都可能失去可見性。
攔截敏感值與主機相依性
計畫檔適合保存變數名稱,不適合保存權杖、密碼、私鑰內容或開發者目錄。請先遞迴擷取已啟用的環境變數:
plutil -convert json -o - App.xctestplan |
jq -r '
.. |
objects |
.environmentVariableEntries? // empty |
.[]? |
select(.enabled == true) |
[.key, .value] |
@tsv
'
檢查輸出時,應優先攔截三類內容:看起來像金鑰的長字串、/Users/某人/ 形式的絕對路徑,以及只存在於互動式 Shell 中的工具路徑。真正的敏感值應由 CI 的受控環境在執行階段注入;測試程式碼只讀取變數名稱,並在缺少值時提供明確錯誤。
避免設定覆寫優先順序失控
XCTestPlan、Scheme、xcodebuild 參數與測試程式碼都能設定啟動參數。建議制定單一優先順序:計畫保存穩定的預設值,CI 只注入敏感值與本次執行識別碼,測試程式碼則不修改程序層級設定。如果流水線使用 -only-testing 或 -skip-testing,應將參數寫入可審查的指令碼,不能藏在工作面板的臨時輸入欄位中。
使用固定目標執行並保存證據
先透過 xcrun simctl list devices available 選擇目前節點已安裝的裝置,並將 UDID 作為流水線輸入。相較於模糊指定「最新系統」,固定裝置與執行階段更容易解釋差異。
DEVICE_UDID="${DEVICE_UDID:?Set DEVICE_UDID from simctl}"
RESULT_PATH="${RESULT_PATH:-artifacts/CI.xcresult}"
rm -rf "$RESULT_PATH"
mkdir -p "$(dirname "$RESULT_PATH")"
xcodebuild test \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-testPlan CI \
-destination "platform=iOS Simulator,id=$DEVICE_UDID" \
-resultBundlePath "$RESULT_PATH"
無論成功或失敗,都應保存 .xcresult、完整命令、提交識別碼、Xcode 版本與所選裝置的 UDID。不要只擷取記錄最後幾十行;測試未執行、程序當機與斷言失敗需要不同證據,而結果套件能保留測試層級、附件與診斷資訊。
平行執行應從保守值開始。依賴同一個資料庫、固定連接埠或共用檔案的測試目標,不應直接啟用平行執行。先完整隔離狀態,再逐一允許平行執行,而不是用重試掩蓋競爭條件。
將稽核變成合併門禁
最終門禁應依固定順序執行:驗證 Scheme 能找到計畫、產生正規化檔案、比對基準、掃描敏感值與絕對路徑、核對略過清單,最後才執行測試。如此一來,設定錯誤會在啟動模擬器前就失敗,節省排隊與診斷時間。
提交前可使用以下清單複核:
- Scheme 已共享,計畫檔已納入版本控制。
- 新增的測試目標已加入正確計畫。
- skipped tests 均有原因、負責人與恢復條件。
- 計畫中沒有真實敏感值與個人目錄。
- CI 沒有隱藏的測試範圍覆寫參數。
- 裝置 UDID、Xcode 版本與結果套件都有記錄。
- 正規化規則沒有刪除影響執行語意的欄位。
- 執行失敗時仍會封存
.xcresult。
當 XCTestPlan 的每次變更都能在程式碼審查中被清楚理解,「本機全綠、雲端漏測」便不再是偶發謎題,而會變成一項能在執行前攔截的設定差異。
常見問題
為什麼不能只依賴 Xcode 的共享 Scheme?
Scheme 只定義執行入口,測試目標、設定、參數、環境變數、語言地區與略過清單仍可能由 XCTestPlan 控制,因此 CI 必須同時固定三者。
可以把存取權杖存進 XCTestPlan 嗎?
不可以。計畫檔只保留變數名稱或無敏感性的預設值,真實值應由受控 CI 環境在執行時注入,並避免進入日誌與測試附件。
正規化是否會漏掉重要變更?
只應移除不影響執行語意的自動產生識別碼,並保留測試目標、選取與略過項目、參數、診斷選項及設定名稱。
為開發流程設定固定的雲端 Mac
MangoVM 提供 M4 與 M4 Pro 兩款 Apple Silicon 實體節點。選擇租期、地區與儲存空間附加項目後,即可核對完整訂單明細。