Очень часто во время функционального тестирования веб-приложений тесты завершаются неудачей просто потому, что сервер стал отвечать немного медленнее. Тест падает не дождавшись загрузки страницы или выполнения запроса. После анализа результатов тестирования такие тесты чаще всего приходится перезапускать повторно, чтобы получить действительную картину об ошибке. Увеличение таймаутов в тестах отчасти позволяет решить эту проблему, но невозможно заранее предусмотреть максимальный предел таких ожиданий. Для того чтобы облегчить дальнейший анализ результатов и избавиться от необходимости повторного запуска можно дать таким тестам «второй шанс» (или даже не один, это уже на Ваше усмотрение). «Второй шанс» в данном случае подразумевает под собой немедленный перезапуск провалившегося теста еще один раз или любое заданное количество попыток, до тех пор, пока тест не выполнится успешно.
Первый способ выполнить перезапуск тестов — использование JUnit @Rule. TestRule позволяет добавить логику вокруг выполнения теста, в данном случае — цикл перезапуска тестов. Определите следующее правило в базовом классе тестов:
@Rule public TestRule rerunRule = new TestRule() { public Statement apply(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { int retryCount = 2; for (int i = 1; i <= retryCount; i++) { try { base.evaluate(); System.err.println(description.getDisplayName() + ": was successful after " + i + " attempt(s)"); break; } catch (Throwable t) { if (i == retryCount) { System.err.println(description.getDisplayName() + ": was not successful for " + i + " attempts"); throw t; } } } } }; } };
Для проектов, в которых используются слушатели и ведется документирование, второй вариант возможно будет более предпочтительным. В первом примере все попытки перезапуска runner рассматривает как один тест, который завершается либо успешно, либо ошибкой по истечении количества попыток. В итоге внутренний лог теста получается не разделенный и тяжело прослеживаемый.
Второй способ перезапуска теста заключается в переопределении метода protected void runChild(FrameworkMethod method, RunNotifier notifier)
в кастомном классе BlockJUnit4ClassRunner
. Используется все тот же цикл перезапуска тестов с отслеживанием результатов выполнения.
import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; /** * Предоставляет возможность автоматического перезапуска тестов, * завершившихся неудачей */ public class RerunFailedRunner extends BlockJUnit4ClassRunner { /** * Для фиксирования количества попыток выполнения тестов */ private HashMap<FrameworkMethod, Integer> rerunMethods = new HashMap<FrameworkMethod, Integer>(); public RerunFailedRunner(Class<?> klass) throws InitializationError { super(klass); } @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { FailerListener listener = new FailerListener(); // подключаем listener для определения результата теста notifier.addListener(listener); int retryCount = 2; for (int i = 1; i <= retryCount; i++) { rerunMethods.put(method, i); // здесь подключаются RunListener для логирования super.runChild(method, notifier); // здесь отключаются if (!listener.isTestFailed()) { // если тест выполнился успешно, больше не запускаем break; } } notifier.removeListener(listener); } /** * Изменяем имя метода для отображения в отчете */ @Override protected String testName(FrameworkMethod method) { Integer attempt = rerunMethods.get(method); if (attempt != null && attempt > 1) { return method.getName() + attempt; } else { return method.getName(); } } /** * Слушатель выполнения теста, фиксирующий его неудачное завершение */ private class FailerListener extends RunListener { private boolean isFailed = false; @Override public void testFailure(Failure failure) throws Exception { isFailed = true; } public boolean isTestFailed() { return isFailed; } } }
Метод protected String testName(FrameworkMethod method)
переопределен для того, чтобы получить в отчете описание выполнения всех попыток. Про создание слушателей тестов можно почитать в статье "JUnit4 RunListener отслеживание выполнения тестов"
Добрый вечер. Не знаю, жив ли блог, но вопрос задам) Как мне запустить в аннотации @RunWith одновременно два класса, например слушатель SeleniumRunner и перезапускалку тестов RerunFailedRunner? Или я что-то не так понял?)
Здравствуйте, к сожалению, JUnit не позволяет использовать одновременно несколько раннеров. Вы можете только создать свой раннер, который будет объединять в себе весь необходимый функционал.
Большое спасибо!) Я бы с радостью, но пока квалификация позволяет только пользоваться вашим прекрасным ранером)