Один и тот же коммит может успешно пройти все тесты на компьютере разработчика, но выполнить лишь часть из них на облачном Mac. Чаще всего причина не в тестовом коде, а в незаметном дрейфе .xctestplan: кто-то временно исключил тесты в Xcode, отключил диагностику или сохранил в плане переменные среды, подходящие только для локальной машины. Решение — рассматривать XCTestPlan как входные данные сборки, а не как вспомогательную настройку графического интерфейса.
Сначала зафиксируйте границы тестового контракта
Воспроизводимая точка запуска тестов должна включать как минимум общий Scheme, XCTestPlan, целевое окружение выполнения и параметры командной строки. Сначала убедитесь, что Scheme и план действительно доступны из командной строки:
WORKSPACE="${WORKSPACE:-App.xcworkspace}"
SCHEME="${SCHEME:-App}"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-showTestPlans
Если ожидаемого плана нет в выводе, проверьте, помечен ли Scheme как Shared, добавлен ли файл плана в репозиторий и ссылается ли на него действие Test в Scheme. Не подставляйте отсутствующий план временными параметрами CI: это лишь усилит расхождение между локальной точкой запуска и конвейером.
В плане необходимо явно проверять следующие элементы:
| Элемент | Что нужно подтвердить | Типичный дрейф |
|---|---|---|
| Test Targets | Какие модульные и UI-тесты выполняются | Новая цель не добавлена |
| Selected Tests | Ограничен ли запуск выбранными тестами | В репозиторий попал отладочный выбор |
| Skipped Tests | Есть ли у каждого исключения ответственный и срок | Неуспешный тест скрыт навсегда |
| Configurations | Язык, регион и параметры запуска | Локальная конфигурация переопределила значения по умолчанию |
| Diagnostics | Политики диагностики сбоев, потоков и производительности | Диагностика случайно отключена ради ускорения |
| Parallelization | Для каких целей разрешено параллельное выполнение | Тесты с общим состоянием мешают друг другу |
Исключение теста — не исправление. Любой новый skipped test должен проходить такую же проверку, как изменение кода, с обязательным указанием условий его возврата.
Формируйте читаемое нормализованное сравнение
XCTestPlan — структурированный файл, поэтому при непосредственной проверке исходного текста мешают порядок полей и автоматически созданные идентификаторы. В репозитории можно хранить нормализованную базовую версию, удаляя только идентификаторы конфигураций, которые не влияют на семантику выполнения:
PLAN="App.xctestplan"
CURRENT=".ci/xctestplan.current.json"
BASELINE=".ci/xctestplan.baseline.json"
mkdir -p .ci
plutil -convert json -o - "$PLAN" |
jq -S 'del(.configurations[]?.id)' > "$CURRENT"
diff -u "$BASELINE" "$CURRENT"
При первом подключении вручную проверьте $CURRENT, затем скопируйте его как базовую версию и добавьте в репозиторий. В дальнейшем конвейер должен только формировать текущий файл и выполнять diff. Цели, исключённые тесты, переменные среды, параметры и настройки диагностики необходимо сохранять. Не удаляйте значимые поля ради «чистого сравнения».
Скрипт нормализации также является частью тестовой инфраструктуры. Изменения скрипта и плана следует показывать в одном запросе на слияние, иначе даже однократное ослабление правил фильтрации может сделать весь последующий дрейф невидимым.
Блокируйте секреты и зависимости от хоста
В файле плана уместно хранить имена переменных, но не токены, пароли, содержимое закрытых ключей или каталоги разработчиков. Сначала рекурсивно извлеките включённые переменные среды:
plutil -convert json -o - App.xctestplan |
jq -r '
.. |
objects |
.environmentVariableEntries? // empty |
.[]? |
select(.enabled == true) |
[.key, .value] |
@tsv
'
При проверке вывода прежде всего блокируйте три категории данных: длинные строки, похожие на ключи; абсолютные пути вида /Users/имя/; пути к инструментам, доступным только в интерактивной оболочке Shell. Настоящие секреты должны внедряться во время выполнения из контролируемой среды CI. Тестовый код должен читать только имена переменных и выдавать понятную ошибку, если нужное значение отсутствует.
Не допускайте неуправляемого приоритета переопределений
XCTestPlan, Scheme, параметры xcodebuild и тестовый код могут задавать параметры запуска. Рекомендуется установить единый порядок приоритетов: план хранит стабильные значения по умолчанию, CI внедряет только секреты и идентификатор текущего запуска, а тестовый код не изменяет конфигурацию на уровне процесса. Если в конвейере используются -only-testing или -skip-testing, эти параметры должны находиться в проверяемом скрипте, а не в временном поле ввода на панели задания.
Выполняйте тесты на фиксированной цели и сохраняйте доказательства
Сначала с помощью xcrun simctl list devices available выберите устройство, установленное на текущем узле, и передайте его UDID в качестве входного параметра конвейера. Фиксация устройства и среды выполнения позволяет объяснять расхождения гораздо проще, чем расплывчатое указание «новейшей системы».
DEVICE_UDID="${DEVICE_UDID:?Set DEVICE_UDID from simctl}"
RESULT_PATH="${RESULT_PATH:-artifacts/CI.xcresult}"
rm -rf "$RESULT_PATH"
mkdir -p "$(dirname "$RESULT_PATH")"
xcodebuild test \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-testPlan CI \
-destination "platform=iOS Simulator,id=$DEVICE_UDID" \
-resultBundlePath "$RESULT_PATH"
Независимо от результата необходимо сохранять .xcresult, полную команду, идентификатор коммита, версию Xcode и UDID выбранного устройства. Не ограничивайтесь последними несколькими десятками строк журнала: для пропущенного запуска тестов, сбоя процесса и неуспешного утверждения требуются разные доказательства. Пакет результатов сохраняет иерархию тестов, вложения и диагностические данные.
Начинайте параллельное выполнение с консервативных значений. Не следует сразу включать параллельный режим для тестовых целей, использующих одну базу данных, фиксированный порт или общие файлы. Сначала полностью изолируйте состояние, а затем разрешайте параллельное выполнение по одной цели, не маскируя состояния гонки повторными запусками.
Превратите аудит в обязательную проверку перед слиянием
Итоговая проверка должна выполняться в фиксированном порядке: подтвердить, что Scheme обнаруживает план; сформировать нормализованный файл; сравнить его с базовой версией; проверить секреты и абсолютные пути; сверить список исключённых тестов — и только затем запустить тесты. Так ошибки конфигурации будут обнаружены до запуска симулятора, что сократит время ожидания и диагностики.
Перед отправкой изменений используйте следующий контрольный список:
- Scheme является общим, а файл плана находится под управлением версий.
- Новые тестовые цели добавлены в нужный план.
- Для всех skipped tests указаны причина, ответственный и условия возврата.
- В плане нет настоящих секретов и личных каталогов.
- В CI нет скрытых параметров, переопределяющих область тестирования.
- UDID устройства, версия Xcode и пакет результатов записываются.
- Правила нормализации не удаляют поля, влияющие на семантику выполнения.
.xcresultархивируется даже при неуспешном запуске.
Когда каждое изменение XCTestPlan понятно при проверке кода, ситуация «локально всё зелёное, а в облаке часть тестов пропущена» перестаёт быть случайной загадкой и превращается в различие конфигураций, которое можно заблокировать ещё до выполнения.
Часто задаваемые вопросы
Почему общей схемы Xcode недостаточно?
Схема задаёт точку входа, но XCTestPlan может отдельно менять цели, конфигурации, аргументы, переменные среды, локаль и исключённые тесты. В CI нужно фиксировать все эти уровни.
Можно ли хранить токены доступа в XCTestPlan?
Нет. В плане оставляют только имена переменных или безопасные значения, а секреты передают при запуске из контролируемой среды CI и не допускают их попадания в журналы.
Может ли нормализация скрыть важное изменение?
Да, если фильтр слишком широк. Удаляйте только сгенерированные идентификаторы без исполнительной семантики, сохраняя цели, исключения, параметры, диагностику и названия конфигураций.
Настройте выделенный облачный Mac для конвейера разработки
MangoVM предлагает физические узлы Apple Silicon в двух конфигурациях: M4 и M4 Pro. Выберите срок аренды, регион и дополнительные параметры хранилища, чтобы проверить полную информацию о заказе.