Подтвердить что ты не робот

Как выполнить повторный запуск неудачных тестов JUnit?

Есть ли способ иметь правило JUnit или что-то подобное, что дает каждому неудачному тесту второй шанс, просто пытаясь запустить его еще раз.

Справочная информация. У меня есть большой набор тестов Selenium2-WebDriver, написанных с помощью JUnit. Из-за очень агрессивного времени (только короткие периоды ожидания после кликов) некоторые тесты (1 из 100 и всегда разные) могут завершиться неудачей, потому что сервер иногда реагирует немного медленнее. Но я не могу так долго ждать, что это определенно достаточно долго, потому что тогда тесты будут проводиться навсегда.) - Поэтому я считаю приемлемым для этого варианта использования, что тест зеленый, даже если ему требуется вторая попробуйте.

Конечно, было бы лучше иметь 2 из 3 большинства (повторите неудачный тест 3 раза, и считайте их правильными, если два из тестов верны), но это будет улучшением в будущем.

4b9b3361

Ответ 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).