測試與程式碼品質

Xcode 測試計畫(Test Plan)總整理:依組態執行的方法

測試計畫以 .xctestplan 檔案管理要執行的測試與條件,並整理僅靠 Scheme 設定不足的原因、目標選擇、組態拆分、涵蓋率、執行順序、重複設定及 xcodebuild 整合。

閱讀 12 分鐘
Xcode 測試計畫(Test Plan)總整理:依組態執行的方法 封面圖

測試程式碼累積到一定程度後,會出現新的疑問:「這些測試該用什麼組合執行?」

全部執行時,CI(Continuous Integration,持續整合)每次要花20分鐘;只執行部分測試,又得手動反覆切換勾選。

想分別在韓文與英文環境執行UI測試時,也常找不到合適的方法。

直接說結論,Apple 為了解決這個問題而提供的就是測試計畫(Test Plan)。

Xcode 11 介紹的測試計畫會將「要執行的測試」與「執行條件」分開記錄在檔案中管理。

本文一次整理測試計畫的結構、實務組態與CI整合。

如果還不熟悉撰寫測試程式碼,建議先閱讀用 XCTest 基礎與 Given-When-Then 撰寫第一個測試的方法

什麼是測試計畫

測試計畫是副檔名為.xctestplan的檔案,內容是人類可讀的JSON。加入專案後,從Scheme參照它。

一個檔案包含兩種內容。

重點是它是檔案。.xctestplan可以提交至Git,讓團隊共用相同條件,也能將設定變更納入程式碼審查。

團隊使用時,建議將計畫與參照它的共用Scheme一起進行版本控制。

為什麼只靠Scheme設定不夠

測試計畫出現以前,所有執行條件都在Scheme的Test動作中,這種結構有其限制。

一個Scheme只有一組Test動作設定。因此要區分快速驗證與夜間完整驗證,就得複製Scheme。

複製Scheme時,建置、執行及設定檔設定也會一併複製。為改一個測試條件,等於連其他設定都複製。

測試計畫反轉了這個關係。一個Scheme可連結多個測試計畫,每個計畫又能包含多個組態。

Xcode Scheme與測試計畫組態階層圖,分支為Smoke、Regression、Nightly計畫
不用重寫測試,也能隨意增加執行組合

建立測試計畫

依據Apple目前的說明,Xcode會建立包含Scheme所建置測試目標之所有測試的預設計畫。

  1. 開啟Product > Scheme > Edit Test Plan中的預設計畫並儲存
  2. 在Tests分頁依目標、標籤、套件或函式選擇執行對象
  3. 在Configurations分頁加入共用設定與需要的組態

要建立更多計畫,請使用Product > Test Plan > New Test Plan。舊Scheme可能會顯示Convert to use Test Plans,這是Xcode 11 介紹的既有設定轉換流程

若連結了多個計畫,可在Product > Test Plan > Manage Test Plans指定一個為預設(Default)。未另行指定時便使用它。

在Product > Test Plan啟用要執行的計畫。此時執行Command + U,也就是Product > Test,會讓目前計畫的每個組態各執行一次。請區分預設計畫與目前啟用的計畫。

共用設定與組態的關係

開啟測試計畫編輯器後會看到Tests與Configurations兩個分頁,重點是Configurations。

此分頁分成上下兩層。

  • Shared Settings(共用設定):所有組態繼承的預設值
  • 個別組態:只覆寫共用設定中必要項目的變體

可以把它想成CSS的繼承:共用條件集中寫在一處,各組態只指定不同的項目。

被覆寫的項目會在編輯器中以粗體顯示,一眼就能看出「只有這裡不同」。

可設定的項目很多,常用的包括以下幾項。

項目 決定內容
Arguments / Environment Variables 執行引數與環境變數
Application Language / Region App執行時的語言與地區
Code Coverage 是否收集涵蓋率及目標Target
Execution Order 依字母順序或每次執行隨機排列
Test Repetition Mode 測試重複執行方式
Test Timeouts 單一測試允許的最長執行時間
Runtime Sanitization Address·Thread·Undefined Behavior Sanitizer
Memory Management Malloc Scribble, Malloc Guard Edges, Zombie Objects
Automatic Screen Capture 失敗時是否自動附加螢幕截圖

共通設定放在共用設定,差異放在組態。遵守這個原則,計畫再多也能維持可管理性。

拆分組態的實務範例

最常見的是依語言建立組態。多語言App容易因語言不同而版面跑掉,拆分組態後即可自動以不同語言重複執行相同的UI測試。

  • 共用設定:開啟涵蓋率,失敗時保留螢幕截圖
  • 組態A「Korean」:將Application Language設為韓文
  • 組態B「English」:將Application Language設為英文
  • 組態C「RTL Pseudolanguage」:驗證由右至左書寫的語言

執行一次此計畫後會選定的測試會在每個組態執行一次,結果報告也會依組態分開顯示,能立刻看出只在哪種語言失敗。

記憶體檢查也是如此。依Apple文件,Address Sanitizer可能使用2~3倍記憶體,並讓程式碼變慢2~5倍,必須考量執行成本。

平時組態關閉Sanitizer,另設診斷組態。例如分開Address Sanitizer與Thread Sanitizer組態,在夜間建置執行。

涵蓋率、隨機順序與重複執行

接著看組態中特別實用的三個選項。

程式碼涵蓋率不只是開啟而已,重要的是先決定測量對象。若把相依函式庫也混入同一數值,會難以判讀App程式碼的變化。

選擇「some targets」只指定自己的App目標,才能得到有意義的數值。

Execution Order可選Alphabetical或Random,但不能一概而論地說所有測試預設都是Alphabetical。

在XCTest計畫選Random後,每次執行的順序都會打亂。另一方面,Swift Testing預設會平行執行測試函式,順序也會隨機化。要分開理解兩個框架的行為。

若改變順序就失敗,很可能依賴前一個測試留下的狀態。這是揭露測試間隱藏耦合的有用訊號。這種獨立性在優良單元測試的FIRST原則也很重要。

Test RepetitionXcode 13 新增的選項,可選擇以下重複方式。

模式 行為 用途
Up Until Maximum Repetitions 不論結果,重複至指定的最大次數 測量間歇性失敗的重現率
重複至失敗(-run-tests-until-failure) 重複到發生失敗 追蹤偶爾失敗的測試
Retry on Failure 失敗後重試至指定次數 維持不穩定UI測試的CI通過率

根據Xcode 13版本資訊Maximum Test Repetitions必須指定正整數。官方文件未確認「預設3次」,因此直接確認計畫儲存的值才準確。

命令列以-test-iterations指定次數,也能搭配-run-tests-until-failure-retry-tests-on-failure。此命令列設定優先於計畫的重複設定。

Retry on Failure很方便,但應謹慎使用。靠重試通過會讓不穩定測試繼續存在。

可暫時開啟,但最好另外追查不穩定的原因。

含有CI PIPELINE文字,以及分為提取要求、合併與夜間建置的測試執行管線插圖
PR輕量執行,夜間完整執行。拆分計畫即可做到

在CI使用測試計畫

測試計畫也能直接用於命令列。先以xcodebuild -scheme MyApp -showTestPlans確認與Scheme連結的計畫。-testPlan傳入的是計畫名稱,不是檔案路徑。

xcodebuild test \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -testPlan Smoke \
  -destination 'platform=iOS Simulator,name=iPhone 16'

保留相同Scheme,只切換計畫名稱,就能分割CI工作執行範圍。例如:

  • 每個PR(Pull Request):-testPlan Smoke — 以核心單元測試為主
  • 合併至主分支:-testPlan Regression — 全部單元+UI測試
  • 夜間排程:-testPlan Nightly — 包含Sanitizer與多語言組態

也能依組態進一步拆分。

Apple目前的命令列範例--only-test-configuration只執行指定組態;相反地,--skip-test-configuration會排除指定組態。兩個選項都使用兩個連字號。

若計畫有5個語言組態,可在CI建立5個工作,各分配一個組態。但平行分配是CI系統的工作,不是測試計畫自動分配機器或縮短時間的功能。

xcodebuild test \
  -scheme MyApp \
  -testPlan Localization \
  --only-test-configuration Korean \
  -destination 'platform=iOS Simulator,name=iPhone 16'

常見問題

Q. 測試計畫檔案要提交到Git嗎?

A. 要共用相同執行條件,建議提交。但上傳Git並非Xcode的必要條件,請同時管理計畫與參照它的共用Scheme。

不過JSON讓多人同時修改時容易衝突。依計畫目的拆檔可降低衝突頻率。

Q. 建立多個組態,時間不也會加倍嗎?

A. 沒錯。測試會依組態數量重複執行。

因此多語言、Sanitizer等耗時組態,適合從平時計畫移出,改在夜間計畫執行。

Q. 一個計畫可以同時放單元測試與UI測試嗎?

A. 可以。但單元測試以秒計,UI測試以分鐘計,回饋速度差異很大。

拆成快速與慢速計畫更符合開發流程。

Q. 用Swift Testing撰寫的測試也能放入計畫嗎?

A. 可以。選定目標同時包含XCTest與Swift Testing測試時,可在同一計畫執行。也了解Swift Testing的標籤與平行執行方式,會更容易拆分計畫。

Q. 想排除特定測試怎麼辦?

A. 可在Tests分頁依目標、套件、函式或參數化個別案例取消勾選。Xcode 16以上也能用Swift Testing的Include Tags與Exclude Tags選擇範圍

這只記錄在計畫檔案,不修改程式碼,因此其他計畫仍會照常執行。


測試計畫不是重新撰寫測試的工具,而是決定既有測試要執行哪些、使用什麼條件的工具。

如果一直用複製Scheme拆分條件,請先儲存預設計畫,再整理成目的不同的計畫。在CI中按工作指定計畫與組態可明確界定範圍,但實際縮短時間取決於Runner數量與平行化方式。

先用Product > Scheme > Edit Test Plan儲存預設計畫,再建立一個Smoke計畫。之後加入組態會容易許多。


來源與確認基準

確認基準日為2026年8月16日。選單名稱與命令列寫法優先採用上述Apple官方文件目前的表記;文件未確認的預設重複次數或CI縮時數值不作斷言。