團隊在主應用程式寫入待辦事項數量後,遠端模擬器上的小工具仍顯示舊值。這時反覆呼叫重新整理介面,通常只會增加排查雜訊:寫入共享資料、擴充功能讀取資料、產生時間線,以及系統安排顯示,是四個不同環節。以下以顯示待辦事項數量的 WidgetKit 小工具為例,將每個環節拆成可獨立驗收的條件。
先釐清資料交接邊界
主應用程式與小工具擴充功能不在同一個行程中。一般的 UserDefaults.standard 不會自動成為雙方共用的資料來源。先在專案的兩個 target 啟用同一個 App Group,並將群組識別碼放進雙方都能讀取的專案設定;不要將測試用識別碼寫死在發布設定中。
主應用程式完成業務資料寫入後,透過 UserDefaults(suiteName:) 儲存數量與更新時間。擴充功能則使用相同的 suite 名稱讀取。寫入函式應回傳成功結果或錯誤,而不是在失敗時默默沿用舊值;擴充功能讀取失敗時,則應顯示明確的預設狀態。如果資料結構不只一個整數,應將其編碼為一筆帶有版本號的紀錄,避免出現數量已更新、標題卻仍是舊值的跨欄位組合。
重新整理請求只是通知系統「資料可能變了」;它不負責將資料從主應用程式搬進擴充功能,也不保證小工具會立即重繪。
先在主應用程式寫入後讀回一次,再於擴充功能的讀取處記錄去識別化的版本號。兩端版本不同,問題就在資料交接;版本相同但畫面不同,才繼續檢查時間線與顯示。不要將待辦事項內容、帳戶資訊或完整的共享紀錄寫入診斷日誌。
將時間線計算寫成純函式
時間線項目的內容不應依賴測試執行時的實際時鐘。讓提供器只負責讀取資料並呼叫計算函式,由計算函式接收目前時間與已讀取的快照。以下程式碼示範最小邊界;型別可放在主應用程式與擴充功能共用的原始碼檔案中。
import Foundation
struct CountEntry: Equatable {
let date: Date
let count: Int
let revision: Int
}
func makeEntries(
now: Date,
count: Int,
revision: Int
) -> [CountEntry] {
[
CountEntry(date: now, count: count, revision: revision),
CountEntry(
date: now.addingTimeInterval(15 * 60),
count: count,
revision: revision
)
]
}
第二個項目仍帶著同一份快照,不能只因為經過十五分鐘,就推斷待辦事項數量會改變。提供器取得項目後,可以為時間線設定晚於最後一個項目的重新請求策略;策略指定的是預期檢查時間,不是介面會在幾秒內更新的承諾。收到新的業務資料時,再請求重新載入對應種類的小工具。如果產品要求顯示精確倒數計時,應重新檢視需求,而不是靠密集的時間線項目模擬即時行程。
固定輸入,斷言可控結果
在單元測試中建立固定的 Date、數量與修訂號,斷言結果恰好有兩個項目、日期嚴格遞增,且兩個項目的數量與修訂號一致。再測試數量為零的邊界情況:零應呈現為真實數值,不能被讀取失敗時的預設值掩蓋。這類測試不啟動模擬器,也不等待系統排程,因此失敗時能直接定位計算邏輯。
在雲端模擬器驗證實際交接
先用 xcrun simctl list devices available 找出可用的 iOS 模擬器及其裝置 ID。建置與測試時固定目標 ID,避免執行器換機後,依裝置名稱配對到另一套執行環境:
xcodebuild test \
-project Widgets.xcodeproj \
-scheme Widgets \
-destination "platform=iOS Simulator,id=$SIM_ID" \
-resultBundlePath "$PWD/widget-tests.xcresult"
這裡的專案名稱與 scheme 僅供示範,執行前須換成儲存庫中實際存在的名稱;SIM_ID 應由任務設定明確傳入。每次執行都使用新的結果目錄,避免既有的 .xcresult 讓命令提前失敗。時間線單元測試通過後,再安裝包含主應用程式與擴充功能的建置版本,並在模擬器中加入小工具:先確認預設狀態,再從主應用程式寫入一筆新的修訂並請求重新整理,最後核對擴充功能的讀取紀錄與畫面。
介面驗收只檢查最終內容與修訂號,不將「請求後幾秒內出現」設為硬性斷言。系統可能調整重新整理的時間;若測試需要精確驗證即時顯示,應測試主應用程式中可控的預覽畫面,而不是把系統排程誤當成應用程式計時器。
依症狀定位,而非反覆重新安裝
| 觀察結果 | 優先檢查 |
|---|---|
| 主應用程式讀回的值就不正確 | 寫入時機、suite 名稱與錯誤處理 |
| 主應用程式的值正確,擴充功能仍讀到舊值 | 兩個 target 的 App Group 設定與紀錄版本 |
| 擴充功能讀到新值,項目仍是舊的 | 提供器傳入的參數與時間線建構 |
| 新項目正確,畫面仍是舊的 | 小工具種類、畫面取用的值與系統實際顯示時間 |
排查前,先記下專案提交版本、執行環境版本、裝置 ID 和共享紀錄的修訂號;這些資訊比一張「畫面沒變」的截圖更能說明問題。不要每次失敗後就立刻清除整個模擬器,否則連可重現問題的共享資料也會一併刪除。需要重置時,先保留測試結果與必要的去識別化紀錄,再明確選擇清除應用程式資料或重建模擬器。
將驗收條件交給流水線
自動化任務分成兩層:第一層執行純函式測試,攔截項目順序、零值或修訂號的回歸問題;第二層在固定的模擬器上確認應用程式與擴充功能均已安裝、雙方讀取同一筆紀錄,並以人工方式或透過可控預覽核對版面。小工具的實際重新整理另列為觀察項目,不與第一層的確定性斷言混在一起。
最後檢查三件事:兩個 target 是否指向同一個共享群組、新增資料是否帶有可追蹤但不洩露內容的修訂號,以及測試是否將時間作為輸入,而非等待真實時間流逝。只要這三項成立,下次再出現舊畫面,就能依資料、項目、顯示的順序定位,不必靠反覆請求重新整理來猜測原因。
常見問題
要求重新整理後,小工具為何沒有立刻更新?
重新整理要求不保證立即執行。先確認共享容器已有新值、提供者能讀到該值,再觀察系統實際產生的時間線;不要以固定秒數判定失敗。
時間線測試需要等待模擬器重新整理嗎?
不需要。將目前時間與資料傳入純函式,直接斷言項目的順序與內容;模擬器只用來核對安裝、資料交接和最終顯示。
將固定規格的雲端 Mac 整合至你的工作流程
可按日、週、月或季租用獨享實體節點,以固定硬體資源執行建置、自動化與 MLX 推論工作。