Как перезапустить неудачные тесты JUnit немедленно? - PullRequest
75 голосов
/ 28 ноября 2011

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

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

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

Ответы [ 5 ]

95 голосов
/ 29 ноября 2011

Вы можете сделать это с помощью 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, вы можете определить свой собственный Runner. Вам необходимо расширить BlockJUnit4ClassRunner и переопределить runChild (). Для получения дополнительной информации см. Мой ответ на Как определить правило метода JUnit в комплекте? . В этом ответе подробно описано, как определить, как запускать код для каждого метода в Suite, для которого вам нужно определить свой собственный Runner.

17 голосов
/ 29 мая 2015

Теперь есть лучший вариант.Если вы используете такие подключаемые модули maven, как: surfire или failsefe, есть возможность добавить параметр rerunFailingTestsCount SurFire Api .Этот материал был реализован в следующем билете: Jira Ticket .В этом случае вам не нужно писать свой пользовательский код, а плагин автоматически изменяет отчет с результатами теста.
Я вижу только один недостаток этого подхода: если какой-либо тест не пройден до / после теста, этап теста не будет повторен-ran.

17 голосов
/ 24 декабря 2013

Как по мне, написание кастомного раннера более гибкое решение.Приведенное выше решение (с примером кода) имеет два недостатка:

  1. Он не будет повторять тест, если он не пройден на этапе @BeforeClass;
  2. Это вычисление тестов выполняется немного по-другому (если у вас есть 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);
    }
}
6 голосов
/ 28 ноября 2011

Вы должны написать свой org.junit.runner.Runner и аннотировать свои тесты с помощью @RunWith(YourRunner.class).

3 голосов
/ 15 марта 2019

Предлагаемый комментарий написан на основе этой статьи с некоторыми дополнениями.

Здесь, если какой-то тестовый пример из вашего проекта jUnit получит результат «сбой» или «ошибка», этот тестовый случай будет перезапущен еще раз. Всего здесь мы поставили 3 шанса на успех.

Итак, нам нужно создать класс правил и добавить уведомления "@Rule" в ваш тестовый класс .

Если вы не хотите создавать одинаковые уведомления "@Rule" для каждого вашего тестового класса, вы можете добавить его в свой абстрактный класс SetProperty (если он у вас есть) и продолжить его.

Класс правил:

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class RetryRule implements TestRule {
    private int retryCount;

    public RetryRule (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.out.println(": run " + (i+1) + " failed");
                        System.err.println(description.getDisplayName() + ": run " + (i + 1) + " failed.");
                    }
                }
                System.err.println(description.getDisplayName() + ": giving up after " + retryCount + " failures.");
                throw caughtThrowable;
            }
        };
    }
}

Класс испытаний:

import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

/**
 * Created by ONUR BASKIRT on 27.03.2016.
 */
public class RetryRuleTest {

    static WebDriver driver;
    final private String URL = "http://www.swtestacademy.com";

    @BeforeClass
    public static void setupTest(){
        driver = new FirefoxDriver();
    }

    //Add this notification to your Test Class 
    @Rule
    public RetryRule retryRule = new RetryRule(3);

    @Test
    public void getURLExample() {
        //Go to www.swtestacademy.com
        driver.get(URL);

        //Check title is correct
        assertThat(driver.getTitle(), is("WRONG TITLE"));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...