2026-04-23 Тест-планы iOS, параллельный XCTest и разбор xcresult для CI на арендованном облачном Mac
Владельцы iOS QA и сопровождающие CI, которые гоняют xcodebuild test на арендованных Mac Apple Silicon в HK / JP / KR / SG / US, быстро упираются в потолок ad hoc флагов -only-testing. Этот рунбук от 2026-04-23 нормализует тест-планы, показывает, как параллелить по destination и как отдавать xcresult в объектное хранилище со стабильными именами — в дополнение к безголовым тестам Simulator, изоляции DerivedData и xcresult и параллельному fan-out архивов.
Зачем тест-планы в удалённом CI
*.xctestplan — это версионируемое намерение: перечень целевых тестов, опций и матриц переменных окружения, который команда видит в Git. Это бьёт длинные shell-строки в YAML CI — особенно когда PM спрашивает после точечного релиза, «какие конфигурации реально бежали». Сочетайте планы с одной стабильной схемой; в плане моделируйте Debug vs Release, переключатели санитайзеров или временное отключение шумных UI-сюит без удаления тестов.
Тест-план против «только схема»: таблица решений
| Подход | Где сияет | Эксплуатация по SSH |
|---|---|---|
Закоммиченный .xctestplan + -testPlan Name |
Воспроизводимость на ноутбуках и билдерах; диффы в PR | Высокая — одна команда на шард |
Дефолты схемы + -only-testing |
Спайки и горячие дорожки | Средняя — разрастание строк в YAML |
| Разные схемы под части тест-таргетов | Монорепы с несвязанными приложениями | Ниже — дублирование обслуживания схем |
Шаблон вызова xcodebuild test
Зафиксируйте DEVELOPER_DIR на Xcode, который вы проверили на хосте, затем запускайте тесты с явным путём к бандлу результатов и отдельным DerivedData на задачу. Держите ID задачи в пути для корреляции с self-hosted runner или внутренним планировщиком.
DEVELOPER_DIR=/Applications/Xcode.app xcodebuild test -workspace App.xcworkspace -scheme App -testPlan Nightly -destination 'platform=iOS Simulator,name=iPhone 16' -resultBundlePath "$CI_RESULTS/run-${CI_SHARD_ID}.xcresult" -derivedDataPath "$CI_DERIVED_DATA/${CI_SHARD_ID}"
xcodebuild test на 12-ядерном классе M4, пока конкуренция не начнёт доминировать над wall time.
Параллельные дорожки, шарды и очереди
Две разные оси: (a) несколько симуляторов на одном хосте против (b) отдельные дочерние задачи с одним destination каждая. Вариант (a) соблазнителен по цене, но гонит CoreSimulator и дисковый I/O в нелинейные просадки. Вариант (b) повторяет подход серьёзных команд в Сингапуре и US East: каждый шард владеет префиксом CORESIMULATOR_HOST и DerivedData, грузит свой *.xcresult. Переиспользуйте дисциплину очередей из статьи про параллельный xcodebuild, но с тестовыми бюджетами повторов и таймаутами на шард, на 20–35% мягче, чем у архивных задач — дисперсия старта XCTest реальна.
Владение шардом, именование и перезапуски
Дайте каждой дочерней задаче стабильный CI_SHARD_ID, независимый от имени ветки Git: ветки со слэшами и эмодзи ломают наивный glob в shell. Кодируйте индекс и число в ID, чтобы по логам было читабельно без YAML: например test-3-of-12 лучше shard-uuid для скриптов пейджера. Когда жмут «перезапустить только упавшие тесты», научите оркестратор маппить только упавшие bundle id в списки -only-testing:, сохраняя тот же DEVELOPER_DIR и семейство resultBundlePath — так не появится второй чужой xcresult с почти тем же именем в бакете. Если мержы блокируются на «всё зелёное», задокументируйте, могут ли политики повтора подменять метку шарда; неясность здесь превращает флак в политический инцидент.
В смешанных пайплайнах, где юнит-тесты заканчиваются за минуты, а UI тянутся за обедом, не давайте медленным шардам блокировать быстрый фидбек: публикуйте частичные загрузки xcresult в staging-префикс и промотируйте набор в «релизный» только когда прошёл последний шард — дашборд всё равно покажет wall time по частичным бандлам, не смешивая незавершённые прогоны с зелёным mainline. Паттерн естественно стыкуется с изоляцией из статьи про DerivedData: каждый шард может удалить свой корень DerivedData при уборке, не трогая соседей на том же NVMe.
Бандлы xcresult, слияние и что хранить
Каждый xcresult — самодостаточный отчёт: держите один на шард, а не пытайтесь «склеивать на диске» способами, которые Xcode не гарантирует между версиями. Ниже по конвейеру многие команды экспортируют JUnit или JSON в CI для дашбордов качества и параллельно архивируют сырой xcresult для интерактивного разбора. Согласуйте ретеншн с политикой NVMe для dSYM: см. dSYM и символизацию падений, когда тесты и сборки делят хост.
Матрица разбора флаков XCTest на bare metal
| Паттерн | Вероятная причина | Первое действие |
|---|---|---|
| 1 из 6 падений, тесты трогают UI и анимации | Таймаут против дисперсии рендера симулятора | Стабилизировать анимации; поднять таймаут + сохранять screen recording в артефактах |
| Падает весь класс в одном шарде | Шард-специфичное окружение, нет сида данных | Проверить паритет env; монтировать фикстуры read-only с общего тома |
| Все шарды замедлились после понедельника | Патч ОС + смена рантайма симулятора | Перебазировать p95, явно зафиксировать версии Xcode/рантайма |
NVMe, опции 1–2 ТБ и «почему не безлимитный CI?»
Тестовые дорожки умножают временный I/O быстрее, чем только архивы. На конфигурациях 1 ТБ или 2 ТБ арендованного Mac планируйте еженедельные зачистки: удаляйте 7-дневные xcresult для зелёных веток, держите 30 дней для дефолтных веток и 90 дней для тегов, связанных с App Store Connect — комплаенс может ужесточить. Когда нестабильность подписи смешивается с флаками тестов, перепроверьте гайд по связке ключей и профилей, чтобы не читать вывод XCTest неверно.
Связанные гайды MacXCode
Соедините рунбук с базой тестов Simulator, справкой по схемам доступа и выбором узлов, когда делите флоты тестов и архивов.
FAQ: тест-планы в безголовых средах
| Вопрос | Практический ответ |
|---|---|
| Нужен ли VNC для отладки тестов? | Редко для автоматизации — опирайтесь на артефакты и VNC как break-glass для UI. |
| Сколько шардов на Mac? | Стартуйте с ядер CPU / 3 для UI-тестов; поднимайте только после недели стабильного p95. |
| Смешивать unit и UI в одном плане? | Ок для маленьких приложений; в крупных репах разделяйте планы по слоям, чтобы укоротить критический путь к сигналу. |
Почему bare metal Mac mini M4 подходит XCTest-тяжёлому CI
Узлы Mac mini M4 на MacXCode дают детерминированную производительность CoreSimulator и NVMe не за шумным гипервизором — ровно то, на что смотрят метрики p95 XCTest. С регионами HK · JP · KR · SG · US можно колокировать тест-билдеры рядом с архивными хостами в тех же SSH-процессах, добавить 1–2 ТБ артефактным организациям и эластично наращивать хосты из тарифов, когда бюджеты повторов показывают упор в I/O или CPU — а не в ошибочной конфигурации тест-планов.
Добавьте облачную мощность Mac под тесты
M4 · NVMe · Несколько регионов