После обновления версии одного новостного приложения сотни мегабайт повторно загружаемых офлайн-ресурсов оказались в Application Support. Все функциональные тесты прошли, пользовательские данные не потерялись, но размер резервной копии резко вырос. Проблема заключалась не в логике загрузки, а в том, что команда так и не оформила исполняемые правила, определяющие, какие файлы необходимо восстанавливать, а какие можно создать заново. Облачный Mac хорошо подходит для таких регрессионных проверок: среда остается неизменной, симулятор можно сбросить, а при сбое — сохранить список содержимого контейнера для диагностики.
Сначала определите семантику восстановления данных
Не начинайте с тестов отдельных каталогов — сначала классифицируйте данные по назначению. Один и тот же JSON-файл может содержать как созданный пользователем материал, так и обычный кэш API; требования к их резервному копированию принципиально различаются.
| Тип данных | Рекомендуемое расположение | Ожидаемое резервное копирование | Примеры |
|---|---|---|---|
| Созданные пользователем данные, которые невозможно восстановить иначе | Documents |
Необходимо сохранять | Черновики, импортированные пользователем файлы |
| Постоянное состояние приложения | Application Support |
Зависит от бизнес-требований | База данных, прогресс редактирования |
| Повторно создаваемые или загружаемые данные | Library/Caches |
Не должны зависеть от резервной копии | Кэш изображений, офлайн-индекс |
| Промежуточные файлы разовой задачи | tmp |
Не сохранять | Каталог распаковки, фрагменты файлов |
Чаще всего неправильно используют Application Support. Этот каталог подходит для данных, которыми приложение управляет длительное время, но это не означает, что все его содержимое должно попадать в резервную копию. Если крупные модели, пакеты карт или прокси-файлы мультимедиа можно получить повторно, для них следует явно установить атрибут исключения либо перенести их в 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 выполнится успешно, только если целевое приложение установлено, а соответствующий симулятор запущен. CI не должен молча выбирать произвольное уже запущенное устройство. В начале задачи необходимо создать или явно указать устройство и дождаться завершения его запуска, а после выполнения задачи — выключить его. Если на Runner параллельно выполняется несколько наборов тестов, отдельный набор устройств симулятора для каждой задачи поможет избежать смешивания контейнеров.
Безопаснее записывать в список файлов только относительные пути — их также проще сравнивать. Не включайте в артефакты домашний каталог, абсолютные пути рабочей области или тестовые учетные данные. Если нужно проверить расширенные атрибуты, вывод xattr можно приложить в качестве диагностического материала, однако решение о прохождении проверки по-прежнему должно основываться на значениях ресурсов Foundation и перечне бизнес-правил. Это исключит зависимость от низкоуровневого поведения, стабильность которого не гарантируется.
Превращайте аномалии размера в понятные сбои
Проверки одного лишь булева атрибута недостаточно. После рефакторинга путей все кэши могут оказаться в Documents, хотя тест исключающего атрибута каждого отдельного файла ничего не выявит. Для каталогов верхнего уровня контейнера можно задать лимиты объема, но они должны указывать на структурные аномалии, а не требовать неизменного числа байтов.
Например, после выполнения тестовой фикстуры в Documents должны находиться только заданные образцы, tmp должен быть пуст по окончании задачи, а Caches может увеличиваться, но не должен содержать файлы с расширениями пользовательских черновиков. Для крупных тестовых ресурсов используйте явный белый список, а в сообщении об ошибке указывайте относительный путь, размер файла, соответствующее правило и этап создания.
Не удаляйте данные сбоя сразу
Первой реакцией на сбой не должен быть сброс симулятора. Сначала архивируйте следующие данные:
- пакет результатов XCTest и названия завершившихся сбоем тестов;
- список относительных путей в контейнере приложения;
- суммарный размер каждого каталога верхнего уровня;
- идентификатор коммита, Scheme и среду выполнения симулятора, использованные в тесте;
- шаг теста, инициировавший создание файла.
Этих материалов достаточно, чтобы различить ситуации, когда «атрибут не установлен», «файл помещен не в тот каталог» и «процедура очистки не была выполнена». Удаляйте устройство только после сохранения всех вложений, чтобы от эпизодического сбоя не осталась одна строка утверждения.
Спроектируйте стабильную проверку для CI
Тесты границ резервного копирования следует запускать при каждом изменении слоя хранения, загрузчика, миграции базы данных или процесса экспорта. Их также можно включить в облегченный набор тестов перед слиянием. Не связывайте их со сквозными сценариями, которым требуются внешние сервисы: воспроизводимость обеспечивают локальные фикстуры и данные фиксированного размера.
Рекомендуется разделить сбои на три категории: ошибочное расположение пользовательских данных, которое обязательно блокирует процесс; отсутствие атрибута исключения, которое также обязательно блокирует процесс; и изменение тенденции размера, вызывающее только предупреждение. Пороговые значения объема должны задаваться конфигурацией в репозитории, а причины их изменения — объясняться при проверке кода. Скрипт не должен автоматически ослаблять лимит после его превышения.
В завершение предусмотрите проверку на реальном устройстве. Симулятор позволяет стабильно тестировать каталоги, атрибуты и код миграции, но не охватывает все аспекты фактического процесса восстановления. Для проверки релиза следует выбрать минимальный набор данных и убедиться, что невосстановимые из других источников данные возвращаются, а повторно создаваемые данные не становятся обязательным условием восстановления. Таким образом, автоматизированная проверка отвечает за частые регрессии, а процесс на реальном устройстве подтверждает итоговые границы — эти проверки не заменяют друг друга.
Часто задаваемые вопросы
Какие файлы iOS обычно нужно исключать из резервной копии?
Повторно создаваемые кэши, загружаемые офлайн-пакеты, промежуточные результаты распаковки и временный экспорт следует хранить в Caches или tmp либо помечать через isExcludedFromBackup.
Почему недостаточно проверить только каталог файла?
Код миграции и зависимости могут поместить файл не туда. Поэтому тест должен учитывать назначение данных, атрибут исключения и ожидаемое восстановление после переустановки.
Заменяет ли симулятор проверку на физическом устройстве?
Нет. Симулятор подходит для постоянного контроля каталогов и метаданных, но перед выпуском нужно отдельно проверить реальное резервное копирование, восстановление и защиту данных на физическом устройстве.
Запустите следующую задачу на облачном M4 Mac
Выберите Runner M4 или Runner M4 Plus и настройте узел, срок аренды и дополнительные параметры хранилища под свой проект. Каждый заказ включает выделенную физическую машину, а не виртуальную.