MangoVM 工程指南

雲端 Mac 的 XCTestPlan 設定漂移稽核實作

雲端 Mac 的 XCTestPlan 設定漂移稽核實作

同一個提交在開發者電腦上跑完所有測試,進入雲端 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 能找到計畫、產生正規化檔案、比對基準、掃描敏感值與絕對路徑、核對略過清單,最後才執行測試。如此一來,設定錯誤會在啟動模擬器前就失敗,節省排隊與診斷時間。

提交前可使用以下清單複核:

當 XCTestPlan 的每次變更都能在程式碼審查中被清楚理解,「本機全綠、雲端漏測」便不再是偶發謎題,而會變成一項能在執行前攔截的設定差異。

常見問題

為什麼不能只依賴 Xcode 的共享 Scheme?

Scheme 只定義執行入口,測試目標、設定、參數、環境變數、語言地區與略過清單仍可能由 XCTestPlan 控制,因此 CI 必須同時固定三者。

可以把存取權杖存進 XCTestPlan 嗎?

不可以。計畫檔只保留變數名稱或無敏感性的預設值,真實值應由受控 CI 環境在執行時注入,並避免進入日誌與測試附件。

正規化是否會漏掉重要變更?

只應移除不影響執行語意的自動產生識別碼,並保留測試目標、選取與略過項目、參數、診斷選項及設定名稱。

獨享實體節點

為開發流程設定固定的雲端 Mac

MangoVM 提供 M4 與 M4 Pro 兩款 Apple Silicon 實體節點。選擇租期、地區與儲存空間附加項目後,即可核對完整訂單明細。

選擇設定並訂購