# Нестабильные тесты Xcode {#xcode-flaky-tests}

> [!WARNING]
> **Требования**
>
> - <.localized_link href="/guides/features/test-insights/xcode">Настроены инсайты тестирования Xcode</.localized_link>. Tuist обнаруживает нестабильные тесты по событиям инсайтов, которые загружает каждый запуск тестов.

Нестабильные тесты — это тесты, которые при многократном запуске с одним и тем же кодом дают разные результаты (проходят или падают). Они подрывают доверие к набору тестов и отнимают время разработчиков на расследование ложных сбоев. Tuist автоматически обнаруживает нестабильные тесты и помогает отслеживать их со временем.

![Страница «Нестабильные тесты»](/images/guides/features/test-insights/flaky-tests-page.png)

## Как работает обнаружение нестабильности {#how-it-works}

Tuist обнаруживает нестабильные тесты двумя способами:

### Повторные попытки тестов {#test-retries}

Когда вы запускаете тесты с функцией повторных попыток, Tuist анализирует результаты каждой попытки. Если тест падает в некоторых попытках, но проходит в других, он помечается как нестабильный.

Передайте `-retry-tests-on-failure` или `-test-iterations` через `tuist xcodebuild test`:

```sh
tuist xcodebuild test \
  -scheme MyScheme \
  -retry-tests-on-failure \
  -test-iterations 3
```

![Детали нестабильного тест-кейса](/images/guides/features/test-insights/flaky-test-case-detail.png)

### Обнаружение между запусками {#cross-run-detection}

Даже без повторных попыток тестов Tuist может обнаружить нестабильные тесты, сравнивая результаты разных запусков CI для одного коммита, схемы и указанного назначения (имя устройства, платформа и версия ОС). Если тест проходит в одном подходящем запуске CI, но падает в другом, неудачное выполнение помечается как нестабильное. Это работает независимо от того, какой результат пришел первым.

Один и тот же тест может стабильно вести себя по-разному в разных схемах или назначениях. Например, прохождение на iOS 26 и падение на iOS 27 не означает нестабильность. Результаты из разных схем, устройств, платформ или версий ОС не помечают друг друга как нестабильные.

Результаты без назначаемого назначения сравниваются с другими неназначенными результатами отдельно от результатов с известным назначением. Сюда входят отчеты с отсутствующими или неполными метаданными назначения и отчеты, содержащие несколько различных назначений, поскольку отдельные исходы тестов пока нельзя отнести к конкретному назначению. Различия между средами в этой группе все еще могут приводить к ложной классификации нестабильности. Обнаружение на основе повторных попыток продолжает работать. Существующие исторические классификации нестабильности остаются без изменений.

Это особенно полезно для выявления нестабильных тестов, которые падают недостаточно последовательно, чтобы их поймали повторные попытки, но все равно вызывают периодические сбои в CI.

## Управление нестабильными тестами {#managing-flaky-tests}

### Автоматическая очистка

Обнаружение и очистка выполняются через **автоматизацию-оповещение** проекта. У каждого проекта есть автоматизация «Обнаружение нестабильных тестов» по умолчанию, чей *триггер* помечает тест как нестабильный, а *восстановление* снимает флаг, когда тест прошел настроенное окно восстановления (по умолчанию **14 дней**) без повторного срабатывания триггера. Отредактируйте её в разделе **Настройки → Автоматизации**, чтобы изменить окно восстановления, заменить действия восстановления (например, также снять карантин) или полностью отключить восстановление, чтобы тесты оставались помеченными как нестабильные, пока вы не снимете метку вручную.

### Ручное управление

Вы также можете вручную пометить или снять пометку нестабильности с тестов на странице деталей тест-кейса. Это полезно, когда:
- Вы хотите подтвердить известный нестабильный тест, работая над исправлением
- Тест был ошибочно помечен из-за проблем с инфраструктурой

## Карантин нестабильных тестов {#quarantining}

Карантин изолирует нестабильный тест, чтобы он не блокировал CI, пока вы его исправляете. По умолчанию это **ручное действие** — вы помещаете в карантин, снимаете карантин и переключаете режимы на странице деталей тест-кейса в дашборде, — но вы также можете подключить это к **автоматизации-оповещению** в разделе **Настройки → Автоматизации**, чтобы тест автоматически игнорировался (или пропускался) при превышении порога нестабильности и снимался с карантина при восстановлении. Каждый переход, ручной или автоматический, записывается в журнал аудита тест-кейса.

По умолчанию восстановление ждет, пока условие триггера перестанет выполняться, поэтому тест остается в карантине, пока его старые нестабильные запуски не выйдут за пределы окна триггера. Чтобы восстановиться быстрее, установите **Восстанавливать, когда** в значение **Нет новых нестабильных запусков**: тест восстановится, когда окно восстановления (дни или запуски) пройдет без нового нестабильного запуска, каждый новый нестабильный запуск перезапускает это ожидание, а после восстановления только новый нестабильный запуск снова поместит его в карантин.

Тест в карантине находится в одном из двух режимов:

- **Игнорирование сбоев**: тест все еще выполняется, но `tuist xcodebuild test` маскирует сбой. Сбои по-прежнему поступают в детектор нестабильных тестов, поэтому вы можете продолжать наблюдать за тестом, не ломая сборку. Выбирайте этот режим для теста, который вы активно исследуете.
- **Пропуск**: xcodebuild получает `-skip-testing <identifier>`, поэтому тест никогда не запускается. Он не создает новых результатов и исчезает с дашборда нестабильных тестов, пока вы не включите его снова. Выбирайте этот режим, когда тест сломан, медленный или настолько постоянно нестабилен, что его запуск просто тратит минуты CI.

### Почему тесты в карантине могут отображаться как прошедшие {#quarantined-passing}

- **Тесты с игнорированием сбоев** все еще выполняются. Тест с игнорированием сбоев, который упал, записывается как **упавший** в запуске тест-кейса и помечается как нестабильный — статус отдельного теста не переписывается. Переопределяется **общий запуск тестов**: если каждый упавший тест-кейс в запуске имеет игнорирование сбоев, весь запуск сообщается как пройденный, поэтому такие сбои не ломают CI.
- **Пропущенные тесты** вообще не запускаются, поэтому дашборд продолжает показывать статус последнего фактического выполнения теста — этот снимок может быть недельной давности.

### Запуск тестов {#running-tests}

`tuist xcodebuild test` — это сквозная обертка, которая автоматически учитывает оба режима. Используйте её так же, как вызывали бы xcodebuild:

```sh
tuist xcodebuild test -scheme MyScheme
```

Пропущенные тесты добавляются к вашему вызову xcodebuild как `-skip-testing <identifier>` и никогда не запускаются. Тесты с игнорированием сбоев запускаются нормально; если они падают, сбой маскируется в итоговом статусе сборки.

#### Обход карантина

`tuist xcodebuild test` принимает `--skip-quarantine` для запуска всего, включая тесты с игнорированием сбоев и пропущенные тесты:

```sh
tuist xcodebuild test --skip-quarantine -scheme MyScheme
```

#### Прямой вызов xcodebuild

Если вы не можете использовать `tuist xcodebuild test`, самостоятельно разверните тесты в карантине в аргументы `-skip-testing` с помощью `tuist test case list`:

```sh
xcodebuild test \
  -scheme MyScheme \
  $(tuist test case list --quarantined --skip-testing)
```

Это безопасное поведение по умолчанию вне `tuist xcodebuild test`: маскировка сбоев для тестов с игнорированием происходит только при использовании этой команды, поэтому пропуск обоих режимов позволяет избежать ложных сбоев CI. Если вам нужен более тонкий контроль, используйте вместо этого `tuist xcodebuild test`.

## Стресс-тестирование новых тестов {#stress-testing-new-tests}

Шлюз стресс-тестирования повторно запускает тест-кейсы, добавленные вашей веткой, несколько раз каждый в новом процессе, и сообщает о тех, которые оказываются нестабильными. Вы увидите новый нестабильный тест в пул-реквесте, который его добавил, а не спустя недели.

Тест-кейс считается новым, если он не запускался в CI за последние 90 дней на основной ветке проекта или на коммите, который впоследствии был в неё включён. При использовании очереди слияния тест-кейс, запущенный очередью, становится известным после включения коммита очереди, и остаётся новым, если очередь его отбрасывает. Коммиты сопоставляются через <.localized_link href="/guides/features/test-insights/xcode#git-history">историю Git</.localized_link>, которую Tuist собирает в CI.

### Включение функции

По умолчанию шлюз отключен. Выберите режим:

- **`report`**: предупреждает о каждом нестабильном тест-кейсе. Запуск завершается со своим собственным результатом.
- **`enforce`**: нестабильный тест-кейс приводит к падению запуска с тем же кодом выхода, что и упавший тест.

Начните с `report` на пару недель, чтобы увидеть, что бы заблокировал `enforce`.

Передайте его перед сквозными аргументами или установите `TUIST_TEST_STRESS_NEW_TESTS`, чтобы менять режим для каждого этапа CI:

```sh
tuist xcodebuild test --stress-new-tests report -scheme MyScheme
```

### Сколько раз каждый тест перезапускается

Это зависит от того, сколько времени занял тест-кейс при первом проходе:

| Первый проход | Повторные запуски |
| --- | --- |
| До 5 с | 10 |
| До 10 с | 5 |
| До 30 с | 3 |
| До 5 мин | 2 |
| Более 5 мин | Не перезапускается |

Повторные запуски используют результаты сборки первого прохода. Проход останавливается после 200 тест-кейсов или 10 минут — в зависимости от того, что наступит раньше, и указывает, какой лимит был достигнут.

### Когда шлюз не запускается

- Первый проход уже упал. Сначала исправьте эти тесты.
- У проекта нет основной ветки, или в ней еще ничего не запускалось в CI.
- Более 30% тест-кейсов проекта выглядят как новые. Повторный запуск такого количества займет слишком много времени. Это ожидаемо для проекта, который только начал отправлять отчеты в Tuist, или после переименования, которое переместило сразу много тестов.

## Уведомления в Slack {#slack-notifications}

Получайте мгновенные уведомления, когда тест становится нестабильным, настроив <.localized_link href="/guides/integrations/slack#flaky-test-alerts">оповещения о нестабильных тестах</.localized_link> в вашей интеграции со Slack.

## Запрос состояния нестабильности и карантина {#querying}

### CLI

Дерево команд `tuist test case` предоставляет всю информацию, которую Tuist отслеживает о тест-кейсе:

```sh
tuist test case list --flaky                       # only flaky test cases
tuist test case list --quarantined                 # only muted or skipped test cases
tuist test case show <test_case_id>                # detail: flakiness rate, last status, run counts
tuist test case events <test_case_id>              # audit log: marked_flaky, muted, skipped, ...
tuist test case run list <test_case_id>            # run history with status and duration
tuist test case run show <test_case_run_id>        # single run, including failure breakdown
```

Все эти команды принимают `--json` для использования в скриптах.

### REST API

Те же данные доступны по HTTP — см. [эндпоинты тест-кейсов](https://tuist.dev/api/docs#tag/test-cases) в справочнике API для полного списка маршрутов, фильтров и полей ответа. Чтобы изменить состояние тест-кейса (пометить или снять пометку нестабильности, игнорировать сбои, пропустить), отправьте `PATCH /api/projects/{account_handle}/{project_handle}/tests/test-cases/{test_case_id}` с параметром `is_flaky` или `state`. То же изменение доступно через `tuist test case update` и инструмент Model Context Protocol `update_test_case`.
