Пропуск тестов
Плагин предоставляет атрибут #[Skip]#[Skip(string $reason = '')]Помечает тест, класс тестов или тестовую функцию как пропущенные, не запуская их., который помечает тест пропущенным. Тест попадает в отчёт со статусом Status::Skipped\Testo\Core\Value\Status::Skipped и учитывается в итогах, а необязательная причина объясняет, почему он пропущен. Пропускайте тест, когда запускать его пока нельзя, а удалять рано: он воспроизводит ещё не починенный баг, сломан незавершённым рефакторингом или написан раньше фичи, которую проверяет.
Класс плагина: SkipPlugin\Testo\Skip\SkipPlugin. Входит в SuitePlugins\Testo\Application\Config\Plugin\SuitePlugins по умолчанию.
#[Skip]
Помечает тест, класс тестов или тестовую функцию как пропущенные, не запуская их.
#[Skip(string $reason = '')]Можно повесить на метод, свободную функцию или класс — на классе пропускаются все тесты тест-кейса. Атрибут наследуется от родительских классов, трейтов и переопределённых методов. Если #[Skip] стоит и на методе, и на классе, действует атрибут метода, и его причина заменяет причину класса. Это верно и для пустой причины: если на методе стоит #[Skip] без аргумента, тест пропускается без причины, а причина класса не подставляется. Повесить атрибут на один элемент дважды нельзя.
Атрибут действует только на обычные тесты: на не-тестовом методе он ничего не делает, а #[Bench]#[Bench(array $callables, array $arguments = [], int $warmup = 1, int $calls = 1_000, int $iterations = 10)]Объявляет бенчмарк для сравнения производительности метода с альтернативными реализациями. и #[TestInline]#[TestInline(array $arguments, mixed $result = null)]Объявляет встроенный тест на методе или функции. выполняются как обычно. По смыслу близок к @Disabled в JUnit и #[ignore] в Rust.
Параметры:
$reason- Почему тест пропущен. По умолчанию причины нет. Заданная причина дописывается в сообщение результата.
Примеры:
Пропустить один тест:
use Testo\Skip;
use Testo\Test;
final class OrderTest
{
#[Test]
#[Skip('broken by the pricing rework')]
public function calculatesTotal(): void
{
// никогда не выполняется — в отчёте Skipped с причиной выше
}
#[Test]
public function createsOrder(): void { /* выполняется как обычно */ }
}На классе — пропускаются все тесты тест-кейса, а метод может указать свою причину:
#[Skip('the billing sandbox is down')]
final class BillingTest
{
#[Test]
public function chargesCard(): void { /* ... */ }
#[Test]
#[Skip('flaky since the gateway upgrade')] // эта причина заменяет причину класса
public function refundsCard(): void { /* ... */ }
}Что не запускается
Решение о пропуске принимается ещё до старта теста: тест сразу получает статус Status::Skipped\Testo\Core\Value\Status::Skipped, и на этом всё. Поэтому ничего из того, что обычно готовит, оборачивает или повторяет тело теста, не срабатывает:
- Хуки #[BeforeTest]
#[BeforeTest(int $priority = 0)]Выполняет метод перед каждым тестом в классе. и #[AfterTest]#[AfterTest(int $priority = 0)]Выполняет метод после каждого теста в классе. не вызываются. - Провайдеры данных, например #[DataProvider]
#[DataProvider(callable|string $provider)]Предоставляет данные для параметризованного теста из метода или вызываемого объекта., тоже не вызываются. Параметризованный тест даёт в отчёте одну запись Status::Skipped\Testo\Core\Value\Status::Skipped, а не по записи на каждый набор данных. - #[Retry]
#[Retry(int $maxAttempts = 3, bool $markFlaky = true)]Объявляет политику повторного запуска теста при падении. и #[Repeat]#[Repeat(int $times = 2, int $maxFailures = 0, bool $markFlaky = true)]Запускает тест фиксированное число раз и решает итог по порогу падений. не запускают повторы. - #[RunInFiber]
#[RunInFiber(Schedule $schedule = Schedule::Solo)]Запускает тест (метод) или все тесты кейса (класс) в файберах под кооперативным планировщиком Testo. не создаёт файбер. - Покрытие кода не собирается.
final class OrderTest
{
#[BeforeTest]
public function startTransaction(): void
{
// для calculatesTotal() не вызывается — готовить нечего
}
#[Test]
#[Skip('broken by the pricing rework')]
public function calculatesTotal(): void { /* ... */ }
#[Test]
public function createsOrder(): void
{
// для этого теста startTransaction() выполняется как обычно
}
}С хуками уровня класса всё иначе: они привязаны к тест-кейсу, а не к отдельному тесту. #[BeforeClass]#[BeforeClass(int $priority = 0)]Выполняет метод один раз перед всеми тестами в классе. Подходит для дорогой инициализации. и #[AfterClass]#[AfterClass(int $priority = 0)]Выполняет метод один раз после всех тестов в классе. Подходит для очистки ресурсов. выполняются, если в тест-кейсе остался хотя бы один непропущенный тест. Если же пропущены все тесты, эти хуки не вызываются, а экземпляр класса даже не создаётся.
Пропуск в отчётах
В результат теста попадает сообщение из полного имени теста — Класс::метод или полное имя функции для функционального теста — и маркера is skipped via #[Skip]; если указана причина, она дописывается в конец:
Tests\Unit\OrderTest::calculatesTotal is skipped via #[Skip] ==> broken by the pricing rework- Отчёты JUnit (
--log-junit), TeamCity (--teamcity) и HTML показывают это сообщение. - Терминал печатает строку пропущенного теста без этого сообщения.
- Компактный отчёт
--jsonучитывает тест в итогах.
Прогон, в котором пропущены все тесты, считается успешным: Status::Skipped\Testo\Core\Value\Status::Skipped не относится ни к падениям, ни к ошибкам.
Пропуск во время выполнения
Иногда решение о пропуске нельзя принять заранее: тест должен сначала осмотреться и пропустить себя по тому, что обнаружит — нет расширения, недоступен сервис, фикстура оказалась пустой. Для этого бросьте SkipTest\Testo\Core\Exception\SkipTest из тела теста. Тест попадёт в отчёт со статусом Status::Skipped\Testo\Core\Value\Status::Skipped и сообщением исключения.
use Testo\Core\Exception\SkipTest;
#[Test]
public function requiresPdoMysql(): void
{
if (!\extension_loaded('pdo_mysql')) {
throw new SkipTest('pdo_mysql required');
}
// ...
}Оба механизма приводят к одному статусу, но разными путями, и это стоит помнить. Исключение бросается, когда тест уже выполняется: #[BeforeTest]#[BeforeTest(int $priority = 0)]Выполняет метод перед каждым тестом в классе. отработал, аргументы подготовлены (провайдером данных, если он есть), а класс создан, если методу нужен экземпляр. #[Skip]#[Skip(string $reason = '')]Помечает тест, класс тестов или тестовую функцию как пропущенные, не запуская их. объявляется заранее и до всего этого просто не доходит. Отличить их в отчёте просто: маркер is skipped via #[Skip] ставит только атрибут.
Бросайте SkipTest\Testo\Core\Exception\SkipTest только из тела теста. Если бросить его из интерцептора, оно покинет пайплайн, и тест получит статус Status::Aborted\Testo\Core\Value\Status::Aborted, а не Status::Skipped\Testo\Core\Value\Status::Skipped.
Skip, SkipTest или фильтр по группе
Все три не дают тесту выполниться, но различаются тем, когда принимается решение и остаётся ли тест в отчёте:
- Берите #[Skip]
#[Skip(string $reason = '')]Помечает тест, класс тестов или тестовую функцию как пропущенные, не запуская их., когда тест пока не должен запускаться и это решение должно быть видно и в коде, и в отчёте. - Бросайте SkipTest
\Testo\Core\Exception\SkipTest, когда решить может только сам тест — по тому, что он обнаружит во время выполнения. - Берите #[Group]
#[Group(string ...$names)]Помечает класс, метод или функцию одной или несколькими группами для выборочной фильтрации. с ключом--group=!slow, когда с тестом всё в порядке, а запускать его в каждом прогоне незачем — например, он слишком долгий.
| Инструмент | Где принимается решение | В отчёте |
|---|---|---|
#[Skip('…')] | в коде, до запуска | Status::Skipped\Testo\Core\Value\Status::Skipped, с причиной |
throw new SkipTest('…') | внутри теста, во время выполнения | Status::Skipped\Testo\Core\Value\Status::Skipped, с сообщением |
#[Group]#[Group(string ...$names)]Помечает класс, метод или функцию одной или несколькими группами для выборочной фильтрации. + --group=!slow | при запуске раннера | не отображается |