內購模組最難重現的故障,通常不是「按鈕無法點擊」,而是交易狀態洩漏到其他測試案例:第一個測試完成購買後,第二個測試原本應驗證未購買介面,卻直接讀到已解鎖狀態。在雲端 Mac 長期執行迴歸測試時,模擬器、StoreKit 工作階段與應用程式持久化資料必須一併隔離,否則單獨執行測試會通過、整套執行卻失敗,將成為常態。
先將測試邊界分成三層
第一層是純業務狀態機。輸入「未購買、處理中、已購買、已退款、已過期」,檢查功能權限與介面狀態,不啟動真實交易。第二層使用 StoreKit Testing,驗證商品查詢、購買回呼、交易監聽與訂閱變更。第三層才是發佈前的受控驗收,用於核對伺服器端通知與正式商品設定。
這樣拆分可讓大多數迴歸測試在本機完成,也能明確判斷失敗源自業務邏輯、用戶端交易適配,還是外部設定。不要讓每個介面測試都從點擊購買按鈕開始;狀態機單元測試應占多數,StoreKit 整合測試只需涵蓋關鍵路徑。
StoreKit Testing 提供的是可控制的交易環境,而不是正式交易流程的替代品。測試通過代表用戶端能在指定事件下正確運作,不代表外部設定已完成驗收。
建立可供審查的商品目錄
在專案中建立 StoreKit/Local.storekit,商品識別碼必須與程式碼使用的識別碼一致。建議集中定義識別碼,避免散落在檢視與測試中:
enum ProductID {
static let proMonthly = "com.example.app.pro.monthly"
static let proYearly = "com.example.app.pro.yearly"
}
設定檔應納入版本控制,而測試價格、顯示名稱及訂閱週期的修改也必須經過程式碼審查。團隊常見的問題是複製舊設定後只變更顯示名稱,卻沒有修改商品識別碼,導致商品查詢傳回空陣列。
為測試建立獨立 Scheme,例如 StoreKitRegression,並在 Scheme 的 Run 與 Test 選項中選擇同一份 .storekit 檔案。不要依賴開發者的個人 Scheme;只有共享 Scheme 才能由 xcodebuild 找到。
| 檢查項目 | 預期結果 |
|---|---|
| 商品識別碼 | 設定檔、程式碼與斷言完全一致 |
| Scheme | 標記為 Shared 並提交至儲存庫 |
| 訂閱群組 | 同一群組內的升級、降級關係清楚 |
| 本地化 | 測試斷言不依賴容易變動的顯示文案 |
每個測試案例都從乾淨的工作階段開始
使用 StoreKitTest 建立測試工作階段,並在 setUp 中關閉系統對話框、清除交易。測試方法結束後,還要清除應用程式自行儲存的權益快取。
import XCTest
import StoreKitTest
final class PurchaseRegressionTests: XCTestCase {
private var session: SKTestSession!
override func setUpWithError() throws {
session = try SKTestSession(
configurationFileNamed: "Local.storekit"
)
session.disableDialogs = true
session.clearTransactions()
UserDefaults.standard.removeObject(forKey: "cachedEntitlements")
}
func testMonthlyPurchaseUnlocksPro() async throws {
try await session.buyProduct(
identifier: ProductID.proMonthly
)
let unlocked = await EntitlementStore.shared.refresh()
XCTAssertTrue(unlocked)
}
}
交易監聽器必須在發起購買前啟動。否則購買很快完成時,監聽工作可能錯過更新,導致測試偶發逾時。退款、撤銷與訂閱到期也應由工作階段主動觸發,再針對權益重新整理後的結果進行斷言,而不是只檢查購買 API 是否傳回成功。
不要用固定等待掩蓋競爭條件
Task.sleep 只能延後失敗,無法證明狀態已完成更新。更穩妥的做法是讓權益儲存庫公開可觀察狀態,由測試等待明確條件,並設定較短的逾時時間。逾時日誌至少應記錄商品識別碼、目前權益、未完成交易數量與測試名稱,但不得輸出權杖或完整憑證。
使用命令列固定執行入口
先確認雲端 Mac 已安裝的模擬器裝置,再讓 CI 使用固定名稱。若映像檔中的裝置名稱不同,應調整流水線參數,而不是讓指令碼在未提示的情況下任意選擇裝置。
xcrun simctl list devices available
xcodebuild test \
-workspace Example.xcworkspace \
-scheme StoreKitRegression \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-resultBundlePath Artifacts/StoreKitTests.xcresult \
CODE_SIGNING_ALLOWED=NO
只執行模擬器測試時關閉簽章,可減少與交易邏輯無關的失敗。結果目錄必須在執行前清空,或依工作編號分別建立目錄,否則既有的 xcresult 會讓命令直接報錯。
StoreKit 整合測試預設採序列執行會更穩定。需要平行執行時,每個執行單元都應擁有獨立的模擬器、Derived Data 與結果目錄。多個處理程序同時操作同一台模擬器,會讓交易佇列與應用程式資料互相污染。
將失敗設計成可供定位的證據
至少涵蓋首次購買、使用者取消、待處理、退款、訂閱續期與訂閱到期六條路徑。每條路徑都應從三個層面進行斷言:StoreKit 傳回狀態、權益儲存庫狀態與最終介面狀態。只斷言按鈕文字是否變更,會漏掉背景權益未更新的問題。
提交前可依照以下順序檢查:
.storekit檔案與共享 Scheme 已納入版本控制。- 每個測試案例開始前,均已清除交易與本機權益快取。
- 交易監聽器早於購買動作啟動。
- 測試不依賴固定等待或執行順序。
- 每個平行工作都使用獨立的模擬器與產出目錄。
- 發生失敗時保留
xcresult、測試日誌與介面附件。 - 正式發佈前另行核對伺服器端通知與商品設定。
在 HireVM 執行這類工作時,請先在控制台確認目前可選的設定,再依測試分片數量規劃模擬器。內購測試通常更容易受到狀態隔離影響,而非單次編譯速度;先確保一條序列流水線能夠重複通過,再增加並行處理,疑難排解的成本會低得多。
常見問題
StoreKit Testing 能完全取代正式環境的內購驗收嗎?
不能。它適合驗證商品對應、交易狀態、介面更新及錯誤分支;正式發布前仍須另行核對伺服器通知、實際商品設定與完整交易流程。
為什麼內購測試單獨執行成功,放入完整套件後卻失敗?
通常是前一個案例留下交易、續期狀態或本機資料。每個案例開始前都應清除測試工作階段與應用程式狀態,並避免共用同一模擬器。
StoreKit 測試可以平行執行嗎?
可以,但每個執行單元必須使用獨立模擬器及結果目錄。多個程序共用同一模擬器或 StoreKit 工作階段會降低可重現性。
將下一項遠端 Mac 任務部署到專用節點執行
依任務規模選擇兩種在售配置、五個節點,以及日租、週租、月租和季租週期。下單前即可完整核對配置與 USD 金額。