Skip to content
...

Repeat

Плагин предоставляет атрибут #[Repeat]#[Repeat(int $times = 2, int $maxFailures = 0, bool $markFlaky = true)]Запускает тест фиксированное число раз и решает итог по порогу падений. и интерцептор, которые запускают тест фиксированное количество раз подряд. В отличие от #[Retry]#[Retry(int $maxAttempts = 3, bool $markFlaky = true)]Объявляет политику повторного запуска теста при падении., Repeat не смотрит, прошёл ли первый запуск — он всегда выполняет полный цикл. Используйте его, когда нужно убедиться, что тест стабилен на множестве прогонов, или проверить логику с возможным скрытым недетерминизмом. Атрибут можно повесить на метод, функцию или целый класс — в последнем случае политика применяется ко всем тестам внутри.

Плагин не требует настройки.

#[Repeat]

Запускает тест фиксированное число раз и решает итог по порогу падений.

#[Repeat(int $times = 2, int $maxFailures = 0, bool $markFlaky = true)]

Работает с любым типом тестов: обычными, встроенными, бенчмарками. При размещении на классе (Test Case) применяется ко всем тестам внутри. Если очередной прогон получает статус Skipped, Cancelled или Aborted — цикл сразу прекращается и этот статус становится итоговым.

Параметры:

$times
Общее число запусков теста — не дополнительные повторы поверх первого. times: 1 — тест запускается один раз, times: 3 — три раза. Минимум 1. По смыслу совпадает с repeat(times) в Kotlin и @RepeatedTest в JUnit.
$maxFailures
Сколько упавших запусков допустимо до того, как весь цикл будет считаться упавшим. По умолчанию 0 — любое падение прерывает цикл и проваливает тест.
$markFlaky
Помечать ли тест как нестабильный (flaky), если хотя бы один запуск упал, но число падений уложилось в $maxFailures. По умолчанию true.

Примеры:

Прогнать один и тот же тест 5 раз — любое падение валит весь тест:

php
#[Repeat(times: 5)]
public function orderCalculationIsStable(): void
{
    $order = new Order([new Item('A', 10), new Item('B', 20)]);
    Assert::same(30, $order->total());
}

Допустить до двух падений из десяти — полезно, когда подсистема заведомо немного шумит:

php
#[Repeat(times: 10, maxFailures: 2)]
public function externalServiceReturnsExpectedShape(): void
{
    $response = HttpClient::get('https://api.example.com/users/1');
    Assert::same(200, $response->statusCode);
}

На классе — все тесты внутри получают политику повторов:

php
#[Repeat(times: 3)]
final class StabilityTest
{
    public function firstCheck(): void { /* ... */ }

    public function secondCheck(): void { /* ... */ }
}

Допустимые падения

По умолчанию maxFailures: 0 означает, что цикл прерывается на первом же упавшем запуске — то, что нужно, когда каждый запуск обязан быть зелёным. Если поднять порог, Repeat превращается в мягкую проверку стабильности: тест считает, сколько запусков из N упало, и проваливается только тогда, когда счётчик переваливает за лимит.

Если цикл уложился в порог, но хотя бы один запуск всё-таки упал, тест получает статус Flaky (если не передать markFlaky: false). Так редкие падения становятся видны в отчётах, а не молча прячутся за зелёным результатом.

php
// Пройдёт, если упало не больше 1 из 5 запусков. С markFlaky: false тест просто зелёный.
#[Repeat(times: 5, maxFailures: 1, markFlaky: false)]
public function tolerantStabilityCheck(): void { /* ... */ }

Комбинирование с Retry

Repeat и #[Retry]#[Retry(int $maxAttempts = 3, bool $markFlaky = true)]Объявляет политику повторного запуска теста при падении. можно использовать вместе — они ортогональны:

  • Repeat прогоняет тест N раз, чтобы проверить стабильность.
  • Retry перезапускает один упавший прогон, чтобы пережить случайную помеху.

Когда оба атрибута стоят рядом, Repeat работает внутри Retry: каждая попытка Retry выполняет полный цикл запусков, и если число упавших запусков в цикле превысит maxFailures, Retry запускает новую попытку для всего цикла целиком.

php
#[Retry(maxAttempts: 3)]
#[Repeat(times: 5, maxFailures: 1)]
public function noisyButImportantCheck(): void { /* ... */ }

Здесь тест выполняется 5 раз за попытку; если в цикле упало больше одного запуска, цикл считается провальным и Retry запускает новую попытку (всего до 3-х).

Repeat или Retry

Плагины выглядят похоже, но решают противоположные задачи. Выбирайте тот, который соответствует вопросу, который вы задаёте к тесту:

#[Retry]#[Retry(int $maxAttempts = 3, bool $markFlaky = true)]Объявляет политику повторного запуска теста при падении.#[Repeat]#[Repeat(int $times = 2, int $maxFailures = 0, bool $markFlaky = true)]Запускает тест фиксированное число раз и решает итог по порогу падений.
НазначениеПережить случайное падениеПроверить стабильность на множестве прогонов
Останавливается на первом успехеДаНет — всегда $times запусков
Толерантность к падениямНеявно: достаточно одного успехаЯвно через $maxFailures
Типичный сценарийВнешний API, сеть, медленный CIГонки, логика, зависящая от времени, прогрев
Стоимость на здоровом тестеОдин прогонN прогонов каждый раз
Что будет, если один из повторов пропущен или прерван?

Цикл сразу останавливается, и тест получает соответствующий статус — Skipped, Cancelled или Aborted. В $maxFailures засчитываются только завершённые прогоны (passed или failed).