DevOps / CI·CD 23 апреля 2026

2026-04-23 Тест-планы iOS, параллельный XCTest и разбор xcresult для CI на арендованном облачном Mac

MacXCode Engineering Team 23 апреля 2026 ~18 мин чтения

Владельцы 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}"

Цифры к публикации: по умолчанию локальный повтор для известных флаков с жёстким потолком, отслеживайте p95 сюиты выше 18 минут как сигнал к масштабированию и закладывайте 2–4 одновременных процесса 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 и символизацию падений, когда тесты и сборки делят хост.

Дисковая гигиена: CoreSimulator может оставлять гигабайты мёртвых устройств после недели смешанных шардов — чистите в том же окне, что и джанитор изоляции, чтобы свободное место не было «хорошим на бумаге и критичным в 02:00».

Матрица разбора флаков 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 неверно.

Соедините рунбук с базой тестов 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 · Несколько регионов