Як негайно повторно запустити невдалі тести JUnit?


81

Чи є спосіб отримати правило JUnit або щось подібне, що дає кожному невдалому тесту другий шанс, просто спробувавши його запустити ще раз.

Довідкова інформація: У мене є великий набір тестів Selenium2-WebDriver, написаних за допомогою JUnit. Через дуже агресивні терміни (лише короткі періоди очікування після клацання) деякі тести (1 із 100, і завжди інший) можуть провалитися, оскільки сервер іноді реагує трохи повільніше. Але я не можу зробити термін очікування настільки довгим, що він однозначно достатньо довгий, тому що тоді тести будуть тривати назавжди.) - Тому я вважаю прийнятним для цього випадку використання тесту зеленого кольору, навіть якщо йому потрібна друга спробуй.

Звичайно, було б краще мати 2 із 3 (повторити невдалий тест 3 рази і прийняти їх за правильні, якщо два тести правильні), але це було б майбутнім покращенням.


1
Не повинно бути необхідності фіксувати час очікування в селені 2. WebDriver повинен виявляти завантаження сторінки і чекати відповідно. Якщо ви хочете дочекатися чогось іншого, крім завантаження сторінки, наприклад, виконання деякого JavaScript, вам слід скористатися класом WebDriverWait, див .: seleniumhq.org/docs/04_webdriver_advanced.html . Тим не менш, я думаю, що це може бути нормально повторити тести графічного інтерфейсу, я просто хотів пояснити, що у більшості випадків явного часу очікування не потрібно.
Тім Бюте,

Це правда, але я також зазначу, що я працював на деяких справді, дуже бідних серверах, які є "чудовими", але вони мають ДІЙСНО довгий час обертання на певних екземплярах сторінки, а отже, я не хочу зазнати невдачі. Це чудове питання, дякую. (природно, я вважаю за краще, щоб хронометраж ЗАВЖДИ був послідовним, і ми будемо наполягати на цьому, але до цього часу це потрібно буде зробити)
cgp

Якщо ви використовуєте функцію огірка rerun.txt, будь ласка, знайдіть тут
Сугат Манкар

Якщо ви використовуєте функцію огірка rerun.txt, перегляньте відповідь тут.
Сугат Манкар

Відповіді:


107

Ви можете зробити це за допомогою 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");
    }
}

Серцем a TestRuleє те base.evaluate(), що викликає ваш метод тестування. Отже, навколо цього дзвінка ви ставите цикл повторення. Якщо у вашому методі тесту видано виняток (помилка твердження насправді є AssertionError), тоді тест не вдався, і ви спробуєте повторити.

Є ще одна річ, яка може бути корисною. Можливо, ви захочете застосувати цю логіку повторної спроби до набору тестів, і в цьому випадку ви можете додати до класу Retry вище тесту для певної анотації методу. Descriptionмістить список анотацій до методу. Для отримання додаткової інформації про це див. Мою відповідь на тему Як запустити якийсь код перед кожним методом JUnit @Test окремо, не використовуючи @RunWith і AOP? .

Використання власного TestRunner

Це пропозиція CKuck, ви можете визначити власного Бігуна. Вам потрібно розширити BlockJUnit4ClassRunner і перевизначити runChild (). Для отримання додаткової інформації див. Мою відповідь на Як визначити правило методу JUnit у наборі? . Ця відповідь детально описує, як визначити, як запускати код для кожного методу в Suite, для якого вам потрібно визначити власний Runner.


Дякуємо: До речі, кожен, хто спробує це, TestRule - це функція, яка існує з версії 4.9 JUnit
Ральф,

@Ralph Насправді TestRule є заміною MethodRule, яка була введена раніше, приблизно 4.7 IIRC, тому це рішення може застосовуватися до 4.9, але буде дещо іншим.
Matthew Farwell

7
Це було дійсно корисно, але щось, що мені спало на думку: retryCount і повторні спроби можуть ввести в оману імена. Коли повторна спроба дорівнює 1, я вважаю, що він запускає тест, і якщо він не вдається, повторить спробу один раз, але це не так. Ймовірно, змінну слід називати maxTries.
Thomas M.

1
@MatthewFarwell: чи це перезапускає діяльність? Чи є якийсь спосіб, ми можемо це зробити?
Басим Шериф

4
Використання цього методу обмежує те, що повторне тестування виконується без відтворення тестового екземпляра. Це означає, що будь-які поля екземпляра в тестовому класі (або суперкласах) не будуть повторно ініціалізовані, можливо, залишаючи стан із попередніх запусків.
Jonah Graham

19

Зараз є кращий варіант. Якщо ви використовуєте плагіни maven, такі як: surfire або failsefe, є можливість додати параметр rerunFailingTestsCount SurFire Api . Цей матеріал був реалізований у наступному квитку: Jira Ticket . У цьому випадку вам не потрібно писати свій власний код і плагін, автоматично вносити зміни у звіт про результати тесту.
Я бачу лише один недолік цього підходу: якщо якийсь тест не вдався на етапі до / після класу, тест не буде повторно запущений.


Приклад командного рядка Maven: mvn install -Dsurefire.rerunFailingTestsCount = 2
activout.se

18

Що стосується мене, то написання власного бігуна більш гнучке рішення. Рішення, розміщене вище (з прикладом коду), має два недоліки:

  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);
    }
}

2
Проблема в тому, що тест невдалий у методі AfterClass.
user1050755

1
Я не бачу жодної проблеми. Я написав зразок тесту, який запускає тест із зазначеним бігуном, і, здається, це нормально: @RunWith (RetryRunner.class) відкритий клас TestSample {private static int i = 0; @AfterClass public static void testBefore () {System.out.println ("Перед тестом"); i ++; if (i <2) {fail ("Помилка"); }}}
user1459144

6

Ви повинні написати свій власний текст org.junit.runner.Runnerі анотувати свої тести @RunWith(YourRunner.class).


5

Запропонований коментар був написаний на основі цієї статті з деякими доповненнями.

Тут, якщо якийсь тестовий випадок з вашого проекту 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"));
    }
}

0

Ця відповідь побудована на цій відповіді .

Якщо вам потрібно ActivityScenarioстворити вашу (і вашу активність) перед кожним запуском, ви можете запустити її за допомогою try-with-resources. Потім ActivityScenarioзасіб буде закрито автоматично після кожної спроби.

public final class RetryRule<A extends Activity> implements TestRule {
    private final int retryCount;
    private final Class<A> activityClazz;
    private ActivityScenario<A> scenario;

    /**
     * @param retryCount the number of retries. retryCount = 1 means 1 (normal) try and then
     * 1 retry, i.e. 2 tries overall
     */
    public RetryRule(int retryCount, @NonNull Class<A> clazz) {
        this.retryCount = retryCount;
        this.activityClazz = clazz;
    }

    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(ActivityScenario<A> scenario = ActivityScenario.launch(activityClazz)){
                        RetryRule.this.scenario = scenario;
                        base.evaluate();
                        return;
                    } catch (Throwable t) {
                        caughtThrowable = t;
                        Log.e(LOGTAG,
                                description.getDisplayName() + ": run " + (i + 1) + " failed: ", t);
                    }
                }
                Log.e(LOGTAG,
                        description.getDisplayName() + ": giving up after " + (retryCount + 1) +
                                " failures");
                throw Objects.requireNonNull(caughtThrowable);
            }
        };
    }

    public ActivityScenario<A> getScenario() {
        return scenario;
    }
}

Потім ви можете отримати доступ до свого сценарію у своїх тестах, використовуючи getScenario()метод.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.