Способы повторного запуска тестов

Часто в практике мы сталкиваемся с нестабильными тестами, которые хочется перезапустить несколько раз. Если после n-го количества перезапусков тест остаётся неудачным, его признают проваленным.
В этой статье я рассмотрю возможные способы повторного запуска тестов в JUnit 5, с которыми я столкнулась при поиске решений.

1. Аннотация @RepeatedTest из JUnit 5

Данный способ позволяет повторять запуск теста несколько раз с указанием желаемого количества перезапусков и не зависит от результата теста. Данный способ полезен, на мой взгляд, когда нужно проверить тест на стабильность при повторных запусках, или для стресс-тестирования.
Однако в junit 5 появилась возможность добавить количество допустимых провалов теста. Для этого был создан атрибут failureThreshold. Он позволяет задать максимальное допустимое количество неудач (провалов теста) при выполнении теста, аннотированного @RepeatedTest. Если количество провалов превышает это значение, тест считается окончательно проваленным. По умолчанию атрибут имеет Integer.MAX_VALUE, что означает бесконечное количество неудач, это значит, что тест будет повторятся ровно заданное количество в @RepeatedTest независимо от количества неудач.

Пример:

@RepeatedTest(value = 10, failureThreshold = 3) void testWithFailureThreshold() { assertTrue(Math.random() > 0.5, "Тест провален"); }

Тест выполняется 10 раз
Если количество неудач превысит 3, тест считается проваленным.

Это не всегда удобно, т.к. тест выполняется всегда n раз, даже если он успешно проходит. А нам нужно, чтобы успешное прохождение завершало тест, а неудачи приводили к перезапуску. Для этого лучше подходит аннотация @RepeatedIfExceptionsTest, о которой расскажу далее.

2. Плагин rerunner-jupiter

Этот плагин предоставляет аннотацию @RepeatedIfExceptionsTest(repeats = 2), которая позволяет перезапускать только упавшие тесты. Использовать ее можно вместо аннотации @Test и указывая количество перезапусков.

3. Аннотация @TestTemplate

Эта аннотация используется под капотом у @RepeatedTest и @RepeatedIfExceptionsTest. Можно не использовать @RepeatedTest, а применить @TestTemplate. Однако количество перезапусков указывается в реализации интерфейсов TestTemplateInvocationContextProvider и TestTemplateInvocationContext.

Пример:

public class RetryTestJunit implements TestTemplateInvocationContextProvider { private static final int MAX_RETRIES = 3; @Override public boolean supportsTestTemplate(ExtensionContext extensionContext) { return extensionContext.getTestMethod().isPresent(); } @Override public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext extensionContext) { return IntStream.range(0, MAX_RETRIES) .mapToObj(RetryInvocationContext::new); } }

4. Использование настройки maven-surefire-plugin (для Maven)

Если вы используете Maven, можно использовать свойство rerunFailingTestsCount в maven-surefire-plugin.

Настройка в pom.xml:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0</version> <configuration> <rerunFailingTestsCount>2</rerunFailingTestsCount> </configuration> </plugin>

5. Перезапуск тестов вручную (через TestExecutionExceptionHandler)

Этот способ позволяет контролировать количество перезапусков, используя интерфейс TestExecutionExceptionHandler, который перехватывает ошибки теста.

Реализация:

private static final int MAX_RETRIES = 3; @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { context.getTestMethod().ifPresent(method -> { boolean result = retryTest(context, method); if (!result) { System.err.println("Тест был перезапущен " + MAX_RETRIES + " раза и провален."); throw new RuntimeException(throwable.getMessage()); } }); }

Логика повторного запуска:

private boolean retryTest(ExtensionContext context, Method method) { int repeats = 0; while (repeats < MAX_RETRIES) { if (restartTest(context, method)) { return true; } repeats++; } return false; }

Метод для повторного запуска теста:

private boolean restartTest(ExtensionContext context, Method method) { try { method.invoke(context.getRequiredTestInstance()); return true; } catch (Exception e) { return false; } }

Этот способ позволяет гибко управлять перезапусками.

Так же для реализации похожего способа можно использовать TestWatcher.testFailed() или механизм Launcher в JUnit 5 для перехвата ошибок и управления перезапусками.

6. Перезапуск тестов с использованием AspectJ

Можно использовать AspectJ для автоматического перезапуска тестов в случае неудачи:

while (attempt < maxRetries) { try { log.info("Запуск теста: {} (попытка {}/{})", joinPoint.getSignature(), attempt + 1, maxRetries); return joinPoint.proceed(); } catch (Throwable t) { log.warn("Тест {} провалился на попытке {}/{}. Причина: {}", joinPoint.getSignature(), attempt + 1, maxRetries, t.getMessage()); attempt++; } } log.error("Тест {} окончательно провалился после {} попыток", joinPoint.getSignature(), maxRetries); throw lastThrowable;

Каждый из перечисленных методов имеет свои плюсы и минусы и это только часть способов и инструментов, которые позволят перезапустить тест.
А какие способы перезапуска тестов используете вы? 😉

2
1 комментарий