Runner 工程文章

雲端 Mac 建立 iOS 資料備份邊界回歸測試

雲端 Mac 建立 iOS 資料備份邊界回歸測試

一款資訊類 App 在版本更新後,將數百 MB 可重新下載的離線資源放進了 Application Support。功能測試全數通過,使用者資料也沒有遺失,但備份容量卻突然增加。問題不在下載邏輯,而在於團隊從未將「哪些檔案必須還原、哪些檔案可以重建」寫成可執行的規則。雲端 Mac 很適合承擔這類回歸檢查:環境能長期維持一致、模擬器可以重設,失敗時也能保留容器清單以供排查。

先定義資料的還原語意

不要一開始就按照目錄撰寫測試,而應先依資料用途分類。同一個 JSON 檔案可能是使用者創作的內容,也可能只是 API 快取;兩者的備份要求完全不同。

資料類型 建議位置 備份預期 範例
使用者建立且無法重建 Documents 應保留 草稿、使用者匯入的檔案
App 持久狀態 Application Support 依業務需求決定 資料庫、編輯進度
可重新產生或下載 Library/Caches 不依賴備份 圖片快取、離線索引
單次任務的中間檔案 tmp 不保留 解壓縮目錄、分段檔案

Application Support 最容易被誤用。它適合存放由 App 長期管理的資料,但不代表其中所有內容都應納入備份。大型模型、地圖套件或媒體代理檔案如果可以重新取得,就應明確設定排除屬性,或改放到 Caches。

判斷標準不是「這個檔案重要嗎」,而是「裝置還原後,這個檔案是否必須從備份中回來,而且無法從可靠來源重建」。

將規則整理成一份存放於儲存庫內的清單,例如記錄邏輯名稱、相對路徑、還原要求與負責人。測試只負責執行這份約定,不應將散落在程式碼中的偶然路徑當成規範。

使用 XCTest 固定排除屬性

Foundation 提供了比讀取底層延伸屬性更穩定的介面。以下輔助方法會在建立檔案後設定 isExcludedFromBackup,再重新讀取資源值,確認設定已實際寫入。

import XCTest

final class BackupBoundaryTests: XCTestCase {
    private func markExcluded(_ url: URL) throws {
        var values = URLResourceValues()
        values.isExcludedFromBackup = true
        var mutableURL = url
        try mutableURL.setResourceValues(values)
    }

    func testDownloadPackageIsExcludedFromBackup() throws {
        let root = FileManager.default.urls(
            for: .applicationSupportDirectory,
            in: .userDomainMask
        )[0]
        let package = root.appendingPathComponent(
            "Downloads/catalog.bundle",
            isDirectory: false
        )

        try FileManager.default.createDirectory(
            at: package.deletingLastPathComponent(),
            withIntermediateDirectories: true
        )
        try Data("fixture".utf8).write(to: package)
        try markExcluded(package)

        let values = try package.resourceValues(
            forKeys: [.isExcludedFromBackupKey]
        )
        XCTAssertEqual(values.isExcludedFromBackup, true)
    }
}

測試重點不是證明 Foundation 能運作,而是確保正式環境的程式碼在建立檔案後,也會經過同一條設定路徑。更穩妥的做法,是將目錄建立、原子寫入與排除標記封裝到儲存元件中,再由測試直接呼叫該元件。否則,即使測試程式碼成功設定屬性,正式環境的下載器仍可能漏掉設定,這道門禁依然沒有價值。

同時檢查錯誤放置

還要加入反向斷言:使用者草稿不得進入 Caches,暫時匯出的產物也不能留在 Documents。測試固定資料應涵蓋建立、升級遷移與失敗重試,因為路徑偏移經常發生在遷移分支,而不是首次安裝流程。

如果是目錄層級的排除,也要抽查新建立的子檔案。不要假設父目錄目前的屬性能永久取代寫入邏輯;元件每次建立目錄後都主動確認屬性,才能更有效地抵抗舊資料遷移與目錄重建造成的影響。

在模擬器中保留容器證據

單元測試適合驗證規則,模擬器容器檢查則適合回答「失敗時實際寫入了什麼」。先讓測試使用固定裝置與獨立的 DerivedData,避免多個任務共用狀態。

set -euo pipefail

DERIVED_DATA="$PWD/.ci/DerivedData-backup"
RESULT_BUNDLE="$PWD/.ci/BackupBoundary.xcresult"

rm -rf "$DERIVED_DATA" "$RESULT_BUNDLE"

xcodebuild test \
  -workspace Example.xcworkspace \
  -scheme Example \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -derivedDataPath "$DERIVED_DATA" \
  -resultBundlePath "$RESULT_BUNDLE"

APP_DATA="$(
  xcrun simctl get_app_container booted com.example.app data
)"
find "$APP_DATA" -type f -print | LC_ALL=C sort \
  > "$PWD/.ci/app-container-files.txt"

get_app_container 只有在目標 App 已安裝,而且對應的模擬器處於啟動狀態時才會成功。CI 不應默默選擇任意已啟動的裝置;應在任務開始時建立或指定裝置、等待啟動完成,並在任務結束後關閉。若在 Runner 上平行執行多組測試,每個任務應使用獨立的模擬器裝置集,避免容器互相串接。

檔案清單只記錄相對路徑會更安全,也更容易比較。不要將家目錄、工作區絕對路徑或測試憑證寫入產物。若需要檢查延伸屬性,可以將 xattr 輸出作為診斷附件,但門禁結論仍應以 Foundation 資源值與業務清單為準,避免依賴未受保證的底層行為。

將容量異常變成可解釋的失敗

只檢查布林屬性仍然不夠。一次路徑重構可能讓所有快取都進入 Documents,但每個檔案本身都沒有對應的排除屬性測試。可以為容器的頂層目錄設定容量預算,但預算應反映結構異常,而不是追求固定的位元組數。

例如,執行測試固定資料後,要求 Documents 只包含指定樣本;任務結束時 tmp 必須為空;Caches 可以成長,但其中不得出現使用者草稿的副檔名。大型測試資源應使用明確的允許清單,並在失敗訊息中輸出相對路徑、檔案大小、所屬規則與建立階段。

不要直接刪除失敗現場

失敗後的第一個動作不應是清空模擬器。請先封存以下內容:

這些證據足以區分「屬性未設定」、「檔案放錯目錄」與「清理流程未執行」。確認附件收集完成後再刪除裝置,避免偶發失敗最後只剩下一行斷言。

設計成穩定的 CI 門禁

每次修改儲存層、下載器、資料庫遷移或匯出流程時,都應執行備份邊界測試;也可以將其納入合併前的輕量測試集。不要把它綁定到需要外部服務的端對端流程;只有使用本機固定資料與固定大小的資料,結果才能重複重現。

建議將失敗分成三類:必須阻擋的使用者資料位置錯誤、必須阻擋的排除屬性缺失,以及只發出警告的容量趨勢變化。容量門檻應由儲存庫內的設定控制,並在審查中說明調整原因,不能讓指令稿在發現超出限制後自動放寬。

最後仍要保留一次實機驗收。模擬器可以穩定驗證目錄、屬性與遷移程式碼,卻無法涵蓋實際還原流程的全部行為。發布檢查應選取一份最小資料集,確認無法重建的資料可以還原,而可再生資料不會被當成還原的前提。如此一來,自動化門禁負責高頻回歸,實機流程負責驗證最終邊界,兩者不會互相取代。

常見問題

哪些 iOS 檔案通常不應進入備份?

可重新下載或重新產生的快取、離線資源包、解壓縮中間檔與暫存匯出物通常不應備份,應放入 Caches、tmp,或明確設定 isExcludedFromBackup。

為什麼不能只檢查檔案所在目錄?

目錄只代表預設語意,遷移程式、外部套件或舊版本可能把檔案放錯位置,因此還要檢查用途、排除屬性與重新安裝後的還原需求。

模擬器檢查可以完全取代實機驗證嗎?

不可以。模擬器適合持續檢查目錄與屬性規則,發佈前仍應在受控實機流程中驗證實際備份、還原與資料保護行為。

獨享實體節點

在雲端 M4 Mac 上執行下一項工作

選擇 Runner M4 或 Runner M4 Plus,依專案需求決定節點、租期與儲存空間附加選項。每筆訂單皆對應一台獨享實體機,並非虛擬機。

立即租用雲端 Mac