Как выполнить повторный запуск неудачных тестов JUnit?
Есть ли способ иметь правило JUnit или что-то подобное, что дает каждому неудачному тесту второй шанс, просто пытаясь запустить его еще раз.
Справочная информация. У меня есть большой набор тестов Selenium2-WebDriver, написанных с помощью JUnit. Из-за очень агрессивного времени (только короткие периоды ожидания после кликов) некоторые тесты (1 из 100 и всегда разные) могут завершиться неудачей, потому что сервер иногда реагирует немного медленнее. Но я не могу так долго ждать, что это определенно достаточно долго, потому что тогда тесты будут проводиться навсегда.) - Поэтому я считаю приемлемым для этого варианта использования, что тест зеленый, даже если ему требуется вторая попробуйте.
Конечно, было бы лучше иметь 2 из 3 большинства (повторите неудачный тест 3 раза, и считайте их правильными, если два из тестов верны), но это будет улучшением в будущем.
Ответы
Ответ 1
Вы можете сделать это с помощью TestRule. Это даст вам необходимую гибкость. TestRule позволяет вставлять логику вокруг теста, поэтому вы должны реализовать цикл повтора:
public class RetryTest {
public class Retry implements TestRule {
private int retryCount;
public Retry(int retryCount) {
this.retryCount = retryCount;
}
public Statement apply(Statement base, Description description) {
return statement(base, description);
}
private Statement statement(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
Throwable caughtThrowable = null;
// implement retry logic here
for (int i = 0; i < retryCount; i++) {
try {
base.evaluate();
return;
} catch (Throwable t) {
caughtThrowable = t;
System.err.println(description.getDisplayName() + ": run " + (i+1) + " failed");
}
}
System.err.println(description.getDisplayName() + ": giving up after " + retryCount + " failures");
throw caughtThrowable;
}
};
}
}
@Rule
public Retry retry = new Retry(3);
@Test
public void test1() {
}
@Test
public void test2() {
Object o = null;
o.equals("foo");
}
}
Сердцем TestRule
является base.evaluate()
, который вызывает ваш метод тестирования. Таким образом, вокруг этого вызова вы ставите цикл повтора. Если в вашем тестовом методе выбрано исключение (ошибка утверждения на самом деле является AssertionError
), тогда тест не сработал, и вы повторите попытку.
Есть еще одна вещь, которая может быть полезной. Вы можете только применить эту логику повтора к набору тестов, и в этом случае вы можете добавить в класс Retry выше теста для конкретной аннотации к методу. Description
содержит список аннотаций для метода. Для получения дополнительной информации об этом см. Мой ответ на Как выполнить некоторый код перед каждым методом JUnit @Test без использования @RunWith или AOP?.
Использование пользовательского TestRunner
Это предложение CKuck, вы можете определить свой собственный Бегун. Вам нужно расширить BlockJUnit4ClassRunner и переопределить runChild(). Для получения дополнительной информации см. Мой ответ Как определить правило метода JUnit в пакете?. В этом ответе подробно описывается, как определить, как запускать код для каждого метода в Suite, для которого вы должны определить свой собственный Runner.
Ответ 2
Как для меня написать пользовательское бегунье более гибкое решение.
Решение, опубликованное выше (с примером кода), имеет два недостатка:
- Он не будет повторять тест, если он не работает на этапе @BeforeClass;
- Это вычисление тестов выполняется немного по-другому (когда у вас есть 3 попытки,
вы получите тестовые прогоны: 4, успех 1, который может ввести в заблуждение);
Вот почему я предпочитаю больше подходить к написанию пользовательского бегуна. И код пользовательского бегуна может быть следующим:
import org.junit.Ignore;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runner.notification.StoppedByUserException;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
public class RetryRunner extends BlockJUnit4ClassRunner {
private final int retryCount = 100;
private int failedAttempts = 0;
public RetryRunner(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
public void run(final RunNotifier notifier) {
EachTestNotifier testNotifier = new EachTestNotifier(notifier,
getDescription());
Statement statement = classBlock(notifier);
try {
statement.evaluate();
} catch (AssumptionViolatedException e) {
testNotifier.fireTestIgnored();
} catch (StoppedByUserException e) {
throw e;
} catch (Throwable e) {
retry(testNotifier, statement, e);
}
}
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (method.getAnnotation(Ignore.class) != null) {
notifier.fireTestIgnored(description);
} else {
runTestUnit(methodBlock(method), description, notifier);
}
}
/**
* Runs a {@link Statement} that represents a leaf (aka atomic) test.
*/
protected final void runTestUnit(Statement statement, Description description,
RunNotifier notifier) {
EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
eachNotifier.fireTestStarted();
try {
statement.evaluate();
} catch (AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e);
} catch (Throwable e) {
retry(eachNotifier, statement, e);
} finally {
eachNotifier.fireTestFinished();
}
}
public void retry(EachTestNotifier notifier, Statement statement, Throwable currentThrowable) {
Throwable caughtThrowable = currentThrowable;
while (retryCount > failedAttempts) {
try {
statement.evaluate();
return;
} catch (Throwable t) {
failedAttempts++;
caughtThrowable = t;
}
}
notifier.addFailure(caughtThrowable);
}
}
Ответ 3
Теперь есть лучший вариант. Если вы используете плагины maven, такие как: surfire или failsefe, есть возможность добавить параметр rerunFailingTestsCount
SurFire Api.
Этот материал был реализован в следующем билете: Jira Ticket.
В этом случае вам не нужно писать свой собственный код, и плагин автоматически изменяет отчет о результатах испытаний.
Я вижу только один недостаток этого подхода: если какой-либо тест не прошел, то тест класса или не будет повторно запущен.
Ответ 4
Вы должны написать свой собственный org.junit.runner.Runner
и аннотировать ваши тесты с помощью @RunWith(YourRunner.class)
.