После разделения модульных, интеграционных и UI-тестов между несколькими планами Xcode Test Plan команда чаще всего сталкивается не с падением тестов, а с тем, что они вообще выполняются не так, как ожидалось. Кто-то меняет план по умолчанию в локальной Scheme, кто-то временно пропускает тест и коммитит файл, а кто-то обновляет переменные окружения, не синхронизируя изменения с конвейером. Облачный Mac точно выполняет конфигурацию из репозитория, поэтому начинать следует не с дополнительных перезапусков, а с проверки самого плана тестирования как обычного кода.
Зафиксируйте единую отслеживаемую точку входа
Scheme должна быть общим файлом. Обычно он находится по пути App.xcodeproj/xcshareddata/xcschemes/App-CI.xcscheme. Не полагайтесь на xcuserdata в пользовательском каталоге: находящиеся там настройки не будут стабильно попадать в систему контроля версий.
Укажите хранящийся в репозитории файл App-CI.xctestplan в разделе Test Action для Scheme, а в конвейере явно передавайте имя плана. Перед началом работы сборочного узла убедитесь, что план доступен:
set -euo pipefail
xcodebuild \
-workspace App.xcworkspace \
-scheme App-CI \
-showTestPlans
xcodebuild \
-workspace App.xcworkspace \
-scheme App-CI \
-testPlan App-CI \
-destination 'platform=iOS Simulator,name=iPhone 16' \
test
Параметр -testPlan нельзя опускать. Если значение Scheme по умолчанию изменится, команда по-прежнему может завершиться успешно, хотя фактически будет запущен уже другой набор тестов.
Превратите файл плана в проверяемый контракт
.xctestplan — это JSON-файл, поэтому он хорошо подходит для статической проверки. Контроль должен охватывать как минимум четыре группы полей:
| Объект проверки | Требование | Что означает ошибка |
|---|---|---|
| configurations | Должна присутствовать конфигурация CI | Точка входа конвейера удалена или переименована |
| testTargets | Должны присутствовать согласованные цели | Одна из групп тестов не выполняется |
| skippedTests | Допустимы только элементы из списка разрешений | Добавлены пропущенные тесты без объяснения |
| environmentVariableEntries | Секретные значения запрещены, обязательные ключи должны присутствовать | Окружение изменилось либо конфиденциальные данные попали в репозиторий |
Не сравнивайте текст всего JSON-файла с учётом порядка элементов. Xcode может переставлять элементы массивов или добавлять поля, а посимвольное сравнение будет вызывать бессмысленные ошибки. Вместо этого разбирайте структуру и проверяйте только те ограничения, от которых действительно зависит команда.
Задача контроля планов тестирования — не запретить любые изменения, а до слияния сделать заметным, какие тесты перестали выполняться и какие параметры окружения были изменены.
Выявляйте пропущенные цели и тесты с помощью скрипта
Приведённый ниже скрипт проверяет конфигурацию CI, обязательные цели тестирования и незарегистрированные пропуски. Список разрешений должен оставаться коротким, а при проверке кода для каждого элемента следует указывать условие его последующего удаления.
#!/usr/bin/env python3
import json
import sys
from pathlib import Path
plan = json.loads(Path("App-CI.xctestplan").read_text())
required_targets = {"AppTests", "AppIntegrationTests"}
allowed_skips = {
"AppIntegrationTests/testTemporaryServerResponse"
}
config_names = {item["name"] for item in plan.get("configurations", [])}
if "CI" not in config_names:
sys.exit("Missing CI test configuration")
targets = plan.get("testTargets", [])
target_names = {
item.get("target", {}).get("name")
for item in targets
}
missing = required_targets - target_names
if missing:
sys.exit(f"Missing test targets: {sorted(missing)}")
actual_skips = {
test
for item in targets
for test in item.get("skippedTests", [])
}
unexpected = actual_skips - allowed_skips
if unexpected:
sys.exit(f"Unapproved skipped tests: {sorted(unexpected)}")
Запускайте скрипт перед командой тестирования. При ошибке он не должен автоматически изменять файл плана: автоматическое исправление может скрыть намеренное изменение конфигурации разработчиком.
Задайте условия удаления элементов из списка разрешений
Список разрешений не должен превращаться в постоянную свалку. Для каждого элемента необходимы как минимум запись о дефекте, ответственный и условие удаления. Если команда не хочет поддерживать ещё один структурированный файл, обязательный ввод этих сведений можно добавить в шаблон проверки кода, а текущее содержимое списка периодически выводить с помощью скрипта.
Отделите стабильные переменные от секретов конвейера
В плане тестирования удобно хранить параметры, которые не меняются между узлами, например UITEST_MODE=1, фиксированный язык или режим имитации сервисов. Токены доступа, закрытые ключи и одноразовые учётные данные нельзя записывать в файл плана или передавать в виде открытых параметров Scheme.
Конвейер облачного Mac может внедрять секреты перед запуском, после чего тестовый процесс считывает их из переменных окружения. Аудит-скрипт при этом проверяет только наличие ключей и принадлежность значений к разрешённому фиксированному набору. Такой подход сохраняет воспроизводимость плана и не допускает попадания конфиденциальных данных в репозиторий.
Учитывайте также цель, в контексте которой раскрываются переменные. Если план ссылается на переименованный Target, файл может по-прежнему открываться в интерфейсе Xcode, но во время выполнения переменные будут разрешаться не так, как ожидалось. При переименовании проекта проверку плана следует запускать вместе с xcodebuild -list.
Сохраняйте достаточно данных для анализа ошибок
После успешного прохождения проверки запустите полный набор тестов и сохраните пакет результатов в фиксированном каталоге:
rm -rf artifacts/App-CI.xcresult
xcodebuild \
-workspace App.xcworkspace \
-scheme App-CI \
-testPlan App-CI \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-resultBundlePath artifacts/App-CI.xcresult \
test
При ошибке сохраняйте три компонента: текущий .xctestplan, фактически выполненную команду и .xcresult. Одной лишь последней части консольного журнала недостаточно, чтобы определить, произошёл ли сбой в логике теста, не была ли загружена цель или изменилась конфигурация плана.
При работе фиксированных тестовых узлов на ZoneMini статическую проверку также следует повторять перед каждым заданием, а не считать, что длительно работающая среда неизбежно остаётся неизменной. Общая Scheme фиксирует точку входа, план тестирования описывает набор тестов, аудит-скрипт контролирует изменения, а пакет результатов сохраняет доказательства. Только сочетание всех четырёх уровней придаёт утверждению «тесты пройдены» стабильный смысл.
Часто задаваемые вопросы
Почему недостаточно зафиксировать команду xcodebuild в CI?
Команда фиксирует имя Scheme и плана, но содержимое .xctestplan может измениться. Отдельная проверка обнаруживает удалённые цели, новые исключения и изменение переменных окружения.
Нужно ли полностью запрещать skippedTests?
Нет. Временные исключения допустимы в явном списке с причиной, ответственным и условием удаления. Любое незарегистрированное исключение должно останавливать проверку.
Как гарантировать выбор одного плана на рабочем Mac и в CI?
Храните Scheme в xcshareddata/xcschemes, добавьте .xctestplan в репозиторий и всегда передавайте его имя через -testPlan, предварительно проверяя список командой -showTestPlans.
Выберите выделенный облачный Mac для непрерывных задач сборки
Проверьте M4, объём памяти, хранилище, узел и расчётный период, а затем подключите стабильное окружение сборки к существующему конвейеру.