Один и тот же набор UI-тестов может стабильно проходить локально, но случайным образом завершаться по тайм-ауту на удалённом узле. Обычно причина кроется не в коде приложения, а в остаточном состоянии симуляторов, изменяющихся границах групп или чрезмерном параллелизме. Надёжная тестовая матрица должна позволять для каждого запуска ответить на три вопроса: какая среда выполнения использовалась, какие тесты запускались вместе и где сохранены данные о сбое. Ниже показано, как с помощью xcodebuild и simctl организовать воспроизводимый процесс тестирования.
Сначала определите границы матрицы
Не начинайте с перебора всех устройств и версий системы. Сначала выберите основную среду выполнения и одно репрезентативное устройство, добейтесь стабильных базовых результатов и только затем добавляйте комбинации для проверки совместимости. Перед запуском фиксируйте версии Xcode и macOS, идентификатор среды выполнения, номер коммита и список тестов. Имя устройства не подходит в качестве уникального ключа: одновременно могут существовать несколько симуляторов с одинаковым именем. Сохранять следует UDID.
| Параметр | Базовый выбор | Условие расширения |
|---|---|---|
| Системная среда выполнения | Основная версия, поддерживаемая текущим проектом | Перед выпуском добавить минимальную поддерживаемую версию |
| Тип устройства | Один распространённый размер экрана | Добавить размеры при проверке адаптивной вёрстки |
| Группа тестов | Фиксированная группировка по тестовым классам | Разделить дополнительно, если одна группа выполняется слишком долго |
| Уровень параллелизма | Начать с двух групп | Постепенно увеличивать после стабилизации ресурсных показателей |
Цель тестовой матрицы — не собрать как можно больше комбинаций, а получать для одного и того же коммита объяснимые результаты при одинаковых входных данных.
Создавайте одноразовые симуляторы с чистым состоянием
Сначала запросите среды выполнения и типы устройств, которые действительно доступны на узле. Не фиксируйте номера версий непосредственно в скрипте.
xcrun simctl list runtimes
xcrun simctl list devicetypes
xcrun simctl list devices available
Выберите нужные идентификаторы из вывода и создайте отдельное устройство для каждой группы. Имя используется только для удобства чтения; во всех последующих командах указывайте UDID.
RUNTIME_ID="com.apple.CoreSimulator.SimRuntime.iOS-XX-X"
DEVICE_TYPE_ID="com.apple.CoreSimulator.SimDeviceType.iPhone-XX"
UDID=$(xcrun simctl create "ui-shard-01" "$DEVICE_TYPE_ID" "$RUNTIME_ID")
xcrun simctl boot "$UDID"
xcrun simctl bootstatus "$UDID" -b
Не используйте симуляторы, с которыми разработчики работают при повседневной отладке. Тесты могут изменять разрешения, региональные настройки, клавиатуру, данные приложения и фоновые задачи. Одноразовое устройство ограничивает срок жизни этих изменений одним запуском. Если тестам необходим доступ к камере, уведомлениям или геолокации, разрешения следует явно настроить на этапе подготовки. Нельзя рассчитывать, что выданный ранее доступ сохранился.
Собирайте один раз, затем запускайте группы по спискам
Каждая группа матрицы не должна выполнять собственную сборку. Сначала запустите build-for-testing, чтобы получить единый комплект тестовых артефактов, а затем выполняйте для каждой группы test-without-building.
DERIVED_PATH="$PWD/.derived-test"
xcodebuild build-for-testing \
-workspace App.xcworkspace \
-scheme AppUITests \
-destination "generic/platform=iOS Simulator" \
-derivedDataPath "$DERIVED_PATH"
XCTESTRUN=$(find "$DERIVED_PATH" -name "*.xctestrun" -print -quit)
Списки тестов для каждой группы следует хранить в репозитории. Не разделяйте тесты перед запуском по алфавиту или текущей продолжительности: из-за этого границы групп будут постоянно меняться. Например, тесты входа и выхода можно поместить в одну группу, а автономного режима и синхронизации — в другую. Отключите встроенный параллельный запуск Xcode, чтобы уровнем параллелизма управлял только внешний планировщик.
xcodebuild test-without-building \
-xctestrun "$XCTESTRUN" \
-destination "platform=iOS Simulator,id=$UDID" \
-only-testing:"AppUITests/AuthenticationTests" \
-parallel-testing-enabled NO \
-resultBundlePath "$PWD/results/shard-01.xcresult"
Если продолжительность выполнения групп существенно различается, скорректируйте списки на основе медианного времени нескольких последних запусков. После изменения зафиксируйте новую версию списков и запишите сведения об изменениях. Не разрешайте планировщику автоматически перераспределять тесты перед каждым запуском: иначе новый сбой нельзя будет напрямую сопоставить с результатом предыдущего запуска.
Ограничивайте параллелизм и гарантированно освобождайте ресурсы
Большее количество одновременно работающих симуляторов не всегда ускоряет тестирование. Каждая дополнительная группа создаёт новые процессы запуска, графические службы, тестовые процессы и экземпляры приложения. Начните с двух групп и отслеживайте нагрузку на память, загрузку CPU, время запуска и общую продолжительность. Если начинается подкачка памяти, bootstatus завершается по тайм-ауту или продолжительность тестов резко колеблется, немедленно уменьшите уровень параллелизма.
Скрипт должен освобождать ресурсы независимо от того, завершился запуск успешно, с ошибкой или был прерван. Для созданного UDID можно зарегистрировать обработчик завершения, который сначала выключит, а затем удалит устройство, чтобы оно не было случайно использовано в следующем запуске.
cleanup() {
xcrun simctl shutdown "$UDID" >/dev/null 2>&1 || true
xcrun simctl delete "$UDID" >/dev/null 2>&1 || true
}
trap cleanup EXIT INT TERM
Не применяйте глобальную команду удаления ко всем симуляторам узла: на нём могут выполняться другие задания. Обрабатывайте только UDID, созданные и записанные в рамках текущего запуска. Если необходимо сохранить состояние после сбоя, сначала заархивируйте журналы и пакеты результатов, а затем удаляйте устройство.
Отличайте реальные сбои от нестабильных с помощью пакетов результатов
У каждой группы должен быть отдельный путь к файлу .xcresult. Вместе с ним сохраняйте стандартный вывод, время начала и завершения, код выхода и UDID. Повторное использование одного пути перезапишет наиболее ценные данные о первом сбое. После ошибки повторно запустите только соответствующую группу и только один раз, а не всю матрицу целиком.
Если первый запуск завершился с ошибкой, а повторный прошёл успешно, проблема обычно связана с нестабильным ожиданием, анимацией, сетевой зависимостью или состоянием гонки. Если оба запуска завершаются на одном и том же шаге, более вероятна детерминированная регрессия. Если места сбоев различаются, в первую очередь проверьте ресурсы узла и общее состояние, используемое несколькими тестами.
Перед выпуском выполните следующие проверки:
- Среда выполнения и тип устройства выбраны из результатов запроса к текущему узлу.
- Для каждой группы используются отдельный UDID и отдельный каталог результатов.
- Артефакты сборки создаются один раз, а группы не выполняют повторную сборку.
- Списки тестов версионируются и не перемешиваются случайным образом перед запуском.
- Результаты первого сбоя не перезаписываются при повторном запуске.
- После прерывания удаляются только симуляторы, созданные текущим запуском.
Часто задаваемые вопросы
Сколько симуляторов iOS запускать параллельно на одном облачном Mac?
Начните с двух групп и увеличивайте число только после проверки давления на память, загрузки CPU, времени запуска и разброса длительности тестов. При свопинге или тайм-аутах параллельность нужно уменьшить.
Зачем каждой группе тестов нужен отдельный симулятор?
Отдельные симуляторы изолируют данные приложения, разрешения и фоновые процессы. Это исключает взаимное влияние групп и позволяет полностью пересоздавать окружение.
Нужно ли перезапускать весь набор после ошибки?
Нет. Сохраните исходный пакет xcresult и один раз повторите только сбойную группу. Если повтор успешен, отметьте тест как потенциально нестабильный и сравните оба результата.
ArmMacs Cloud Mac
Используйте выделенный физический узел в течение срока проекта
Выберите фиксированные чип, объём памяти, хранилище, срок аренды и доступный узел — при наличии на складе заказ перейдёт к оформлению.