После слияния изменений, перерабатывающих главный экран, все функциональные тесты прошли, но пользователи начали сообщать, что первое открытие приложения стало медленнее. Производительность запуска легко упустить из виду в повседневной разработке: новая миграция базы данных, синхронное чтение конфигурации, инициализация аналитики или загрузка крупного изображения могут незаметно добавить работу в главный поток. Вместо того чтобы ждать, пока проблема проявится при ручном тестировании, лучше превратить измерение холодного запуска на закреплённом облачном Mac в обязательную проверку перед слиянием.
Сначала определите сопоставимый сценарий запуска
Фраза «открыть приложение» — это ещё не полное определение теста. Как минимум необходимо зафиксировать конфигурацию сборки, модель Simulator, версию среды выполнения, исходные данные приложения и аргументы запуска. Цель теста также должна быть единственной и чёткой: например, измерять только время от создания процесса до появления первого интерактивного экрана, не включая в результат запрос авторизации или загрузку тестовых данных.
Рекомендуется создать отдельный план Performance.xctestplan, включить в нём только тесты производительности запуска и отключить случайный порядок выполнения. Для перехода в стабильное состояние тестовая среда использует аргументы запуска:
final class LaunchPerformanceTests: XCTestCase {
override func setUp() {
continueAfterFailure = false
}
func testColdLaunch() {
let app = XCUIApplication()
app.launchArguments = ["-uiTesting", "-resetLocalState"]
let options = XCTMeasureOptions()
options.iterationCount = 8
measure(
metrics: [XCTApplicationLaunchMetric(waitUntilResponsive: true)],
options: options
) {
app.launch()
}
}
}
Аргумент -resetLocalState должен обрабатываться тестовой точкой входа приложения и очищать только локальное состояние, которое можно восстановить. Не используйте этот аргумент в рабочем пути выполнения, чтобы пропускать инициализацию, которую действительно нужно измерять. Иначе результат будет отражать лишь «скорость запуска специальной тестовой версии».
Зафиксируйте состояние облачного Mac и Simulator
Для контроля производительности нужен выделенный физический компьютер, а не общая среда, где неизвестные задачи конкурируют за ресурсы CPU и диска. Перед каждым запуском проверяйте текущий путь к Xcode, доступные экземпляры Simulator и свободное место на диске:
set -euo pipefail
xcode-select -p
xcodebuild -version
xcrun simctl list devices available
df -h "$HOME"
Выберите в плане тестирования один установленный Simulator. Не позволяйте скрипту автоматически выбирать «первое доступное устройство»: при изменении образов исторические данные перестанут быть сопоставимыми. Во время выполнения проверки также не следует параллельно запускать архивирование, обновление зависимостей или масштабную индексацию.
Данные о производительности — прежде всего данные о среде и только затем о коде. Показатели, для которых нельзя указать узел выполнения, конфигурацию сборки и состояние Simulator, не подходят для прямой блокировки слияния.
При первоначальном создании базовой линии выполните тест несколько раз на одном и том же коммите. Если разброс результатов заметно велик, сначала проверьте фоновые задачи, нагрузку на диск и логику сброса тестовых данных, а не расширяйте пороговые значения.
Запускайте единственную блокирующую проверку через xcodebuild
Задача командной строки должна запускать только целевой тест и сохранять пакет результатов как артефакт конвейера. Название устройства необходимо заменить моделью, фактически закреплённой в плане тестирования:
set -o pipefail
mkdir -p Artifacts
xcodebuild test \
-workspace App.xcworkspace \
-scheme App \
-testPlan Performance \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-only-testing:AppUITests/LaunchPerformanceTests/testColdLaunch \
-resultBundlePath Artifacts/Launch.xcresult \
| tee Artifacts/launch-test.log
Параметр set -o pipefail критически важен: без него после сбоя xcodebuild команда tee в конце конвейера всё ещё может привести к успешному завершению задачи. Следует сохранять и Launch.xcresult, и текстовый журнал: первый позволяет просматривать отдельные измерения и вложения теста, а второй помогает быстро находить ошибки на этапе сборки или запуска.
| Проверяемый параметр | Способ фиксации | Действия после изменения |
|---|---|---|
| Xcode | Записывать xcodebuild -version |
Сначала создать новую кандидатную базовую линию |
| Simulator | Зафиксировать модель и среду выполнения | Не сравнивать напрямую со старой базовой линией |
| Конфигурация сборки | Использовать один и тот же план тестирования | Отклонять временную подмену параметров |
| Тестовые данные | Выполнять детерминированный сброс через аргументы запуска | Проверить полноту сброса |
| Число итераций | Всегда использовать 8 запусков | Не принимать решение по одному результату |
Управляйте базовой линией, а не гонитесь за единичным показателем
Создайте в отчёте Xcode базовую линию производительности для выбранного теста и добавьте сформированные общие файлы базовой линии в систему контроля версий. Изменения базовой линии должны проходить ревью так же, как файлы блокировки зависимостей: в описании коммита необходимо указать источник изменения, проверочный коммит и ожидаемое влияние, а не ограничиваться фразой «обновлены данные производительности».
Пороговые значения должны отдельно учитывать предупреждения и блокировку. Небольшие колебания можно оставить для наблюдения за тенденцией, а стабильно выходящие за пределы допустимого диапазона результаты повторных запусков считать сбоем. Основанием для решения служит не единичный лучший результат, а устойчивое отклонение нескольких итераций от проверенной базовой линии в одной и той же среде.
Не следует сразу обновлять базовую линию в следующих случаях:
- на тестовом узле одновременно выполнялись другие ресурсоёмкие задачи;
- изменилась модель Simulator или среда выполнения;
- первичная загрузка зависимостей или индексация ещё не завершилась;
- ради прохождения теста были пропущены этапы инициализации;
- аномалия возникла только в одном запуске, а остальные результаты остались нормальными.
После сбоя сохраните данные и определите ответственный участок запуска
Если блокирующая проверка завершилась сбоем, сначала повторите её на том же облачном Mac и том же коммите. Если результат воспроизводится, разделите запуск на этапы: вход в процесс, внедрение зависимостей, открытие базы данных, построение модели первого экрана, декодирование ресурсов и отрисовка первого кадра. На этих границах можно добавить унифицированные события os_signpost, чтобы последующий анализ показывал, на каком участке возникла задержка, вместо попыток угадать причину по масштабу коммита.
Начинайте диагностику с последних изменений, но не ограничивайтесь прикладным кодом. На путь запуска также влияют изменения конфигурации сборки, параметры диагностики при отладке, увеличение объёма ресурсов и тестовые фикстуры. После исправления сохраните оба файла xcresult — для неудачного и успешного запусков — и укажите сравниваемые коммиты в запросе на слияние.
Ценность надёжной блокирующей проверки заключается не в присвоении времени запуска абсолютной оценки, а в постоянной сопоставимости одного и того же сценария. Зафиксируйте среду, версионируйте базовую линию, сохраняйте подтверждающие данные и проверяйте обновления базовой линии через ревью — только тогда производительность запуска превратится из субъективного ощущения в отслеживаемое инженерное ограничение.
Часто задаваемые вопросы
Сколько раз следует запускать тест производительности?
Для одного коммита нужно не менее 5 запусков, а в проверке перед слиянием разумно использовать 8 и более итераций при неизменных Simulator, конфигурации сборки и фоновых процессах.
Нужно ли повышать порог сразу после сбоя проверки?
Нет. Сначала повторите тест на том же узле и проверьте состояние Simulator и путь запуска. Базовую линию меняют только после подтверждённого и согласованного изменения поведения приложения.
Воспроизводите рабочие процессы на облачном Mac, включая и выключая его по мере необходимости
Выберите подходящую конфигурацию Apple Silicon из трёх вариантов и арендуйте ресурс на день, неделю, месяц или квартал. Вы получаете выделенную физическую машину, а не виртуальную; актуальный статус доступности отображается в консоли в реальном времени.