Wie kann ich fehlgeschlagene JUnit-Tests sofort erneut ausführen?

Lesezeit: 9 Minuten

Wie kann ich fehlgeschlagene JUnit Tests sofort erneut ausfuhren
Ralf

Gibt es eine Möglichkeit, eine JUnit-Regel oder ähnliches zu haben, die jedem fehlgeschlagenen Test eine zweite Chance gibt, indem man einfach versucht, ihn erneut auszuführen?

Hintergrund: Ich habe ein großes Set von Selenium2-WebDriver-Tests mit JUnit geschrieben. Aufgrund eines sehr aggressiven Timings (nur kurze Wartezeiten nach den Klicks) können einige Tests (1 von 100, und immer ein anderer) fehlschlagen, weil der Server manchmal etwas langsamer reagiert. Aber ich kann die Wartezeit nicht so lang machen, dass sie definitiv lang genug ist, denn dann dauern die Tests ewig.) — Daher denke ich, dass es für diesen Anwendungsfall akzeptabel ist, dass ein Test grün ist, auch wenn er eine Sekunde braucht Versuchen.

Natürlich wäre es besser, eine 2 von 3 Mehrheit zu haben (wiederhole einen nicht bestandenen Test dreimal und werte ihn als richtig, wenn zwei der Tests richtig sind), aber dies wäre eine zukünftige Verbesserung.

  • Feste Wartezeiten sollten in Selenium 2 nicht notwendig sein. Der WebDriver sollte das Laden von Seiten erkennen und entsprechend warten. Wenn Sie auf etwas anderes als das Laden der Seite warten möchten, z. B. auf die Ausführung von JavaScript, sollten Sie die Klasse WebDriverWait verwenden, siehe: seleniumhq.org/docs/04_webdriver_advanced.html. Trotzdem denke ich, dass es in Ordnung sein kann, GUI-Tests erneut zu versuchen. Ich wollte nur klarstellen, dass in den meisten Fällen keine explizite Wartezeit erforderlich ist.

    – Tim Büthe

    9. Mai ’12 um 12:21 Uhr

  • Es ist wahr, aber ich möchte auch darauf hinweisen, dass ich an einigen wirklich, wirklich schlechten Servern gearbeitet habe, die “in Ordnung” sind, aber sie haben eine JA WIRKLICH lange Hochfahrzeit auf bestimmten Seiteninstanzen, und deshalb möchte ich nicht scheitern. Das ist eine großartige Frage, danke. (Natürlich wäre mir das Timing lieber STETS konsequent sein, und wir werden darauf drängen, aber bis dahin muss das reichen)

    – vgl

    22. August 2012 um 13:44 Uhr

  • Wenn Sie die rerun.txt-Funktion von Cucumber verwenden, finden Sie meine Antwort hier

    – Sugat Mankar

    4. November 15 um 10:50 Uhr

  • Wenn Sie die rerun.txt-Funktion von Cucumber verwenden, lesen Sie bitte hier.

    – Sugat Mankar

    4. November 15 um 10:52 Uhr

Wie kann ich fehlgeschlagene JUnit Tests sofort erneut ausfuhren
Matthäus Farwell

Sie können dies mit einem tun Testregel. Dadurch erhalten Sie die Flexibilität, die Sie benötigen. Mit einer TestRule können Sie Logik um den Test einfügen, sodass Sie die Wiederholungsschleife implementieren würden:

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

Das Herz eines TestRule ist der base.evaluate(), die Ihre Testmethode aufruft. Um diesen Aufruf legen Sie also eine Wiederholungsschleife. Wenn in Ihrer Testmethode eine Ausnahme ausgelöst wird (ein Behauptungsfehler ist eigentlich eine AssertionError), ist der Test fehlgeschlagen und Sie versuchen es erneut.

Es gibt noch eine andere Sache, die nützlich sein kann. Möglicherweise möchten Sie diese Wiederholungslogik nur auf eine Reihe von Tests anwenden. In diesem Fall können Sie der Retry-Klasse oben einen Test für eine bestimmte Anmerkung zur Methode hinzufügen. Description enthält eine Liste mit Anmerkungen zur Methode. Weitere Informationen dazu finden Sie in meiner Antwort auf Wie man vor jeder [email protected] einzeln Code ausführt, ohne @RunWith oder AOP zu verwenden?.

Verwenden eines benutzerdefinierten TestRunners

Dies ist der Vorschlag von CKuck, Sie können Ihren eigenen Runner definieren. Sie müssen verlängern BlockJUnit4ClassRunner und runChild() überschreiben. Weitere Informationen finden Sie in meiner Antwort auf Wie definiere ich eine JUnit-Methodenregel in einer Suite?. Diese Antwort beschreibt, wie Sie definieren, wie Code für jede Methode in einer Suite ausgeführt werden soll, für die Sie Ihren eigenen Runner definieren müssen.

  • Danke: Übrigens für alle, die das versuchen werden, TestRule ist ein Feature, das seit JUnit Version 4.9 existiert

    – Ralf

    28. November 11 um 20:36 Uhr

  • @Ralph Tatsächlich ist TestRule ein Ersatz für MethodRule, das früher eingeführt wurde, ungefähr 4.7 IIRC, sodass diese Lösung möglicherweise vor 4.9 angewendet werden kann, aber etwas anders wäre.

    – Matthäus Farwell

    29. November 11 um 8:57 Uhr

  • Das war wirklich hilfreich, aber etwas, das mir in den Sinn kam: retryCount und Wiederholungen könnten irreführende Namen sein. Wenn retry 1 ist, würde ich annehmen, dass er den Test durchführt, und wenn er fehlschlägt, es einmal wiederholt, aber das ist nicht der Fall. Die Variable sollte wahrscheinlich maxTries heißen.

    –Thomas M.

    19. Juni 13 um 9:56 Uhr


  • @MatthewFarwell: Startet dies die Aktivität neu? Gibt es eine Möglichkeit, können wir das tun?

    – Basim Scherif

    20. Juli 16 um 9:33 Uhr

  • Die Verwendung dieser Methode hat eine Einschränkung, dass die Testwiederholungen durchgeführt werden, ohne die Testinstanz neu zu erstellen. Das bedeutet, dass alle Instanzfelder in der Testklasse (oder Superklassen) nicht neu initialisiert werden, wodurch möglicherweise der Zustand von früheren Durchläufen verlassen wird.

    – Jonah Graham

    4. Dezember 16 um 6:09 Uhr

Jetzt gibt es eine bessere Option. Wenn Sie Maven-Plugins wie surfire oder failsefe verwenden, gibt es eine Option zum Hinzufügen von Parametern rerunFailingTestsCount SurFire-API. Dieses Zeug wurde im folgenden Ticket implementiert: Jira-Ticket. In diesem Fall müssen Sie Ihren benutzerdefinierten Code und das Plug-in nicht schreiben, um den Testergebnisbericht automatisch zu ändern.
Ich sehe nur einen Nachteil dieses Ansatzes: Wenn ein Test auf der Vorher/Nachher-Klassenstufe fehlschlägt, wird der Test nicht erneut ausgeführt.

  • Beispiel auf der Maven-Befehlszeile: mvn install -Dsurefire.rerunFailingTestsCount=2

    – activout.se

    3. Februar 20 um 12:05 Uhr

Was mich betrifft, so schreibe ich eine flexiblere Lösung für benutzerdefinierte Läufer. Die oben gepostete Lösung (mit Codebeispiel) hat zwei Nachteile:

  1. Es wird den Test nicht wiederholen, wenn er auf der @BeforeClass-Stufe fehlschlägt;
  2. Die Berechnung von Tests läuft etwas anders ab (wenn Sie 3 Wiederholungen haben, erhalten Sie Testläufe: 4, Erfolg 1, was verwirrend sein könnte);

Deshalb bevorzuge ich mehr Herangehensweise beim Schreiben von benutzerdefinierten Läufern. Und der Code des benutzerdefinierten Läufers könnte wie folgt lauten:

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

  • Problem ist, wenn der Test in der AfterClass-Methode fehlschlägt.

    – Benutzer1050755

    26. November 14 um 20:57 Uhr


  • Ich sehe kein Problem. Ich habe einen Beispieltest geschrieben, der den Test mit dem angegebenen Läufer ausführt, und es scheint zu funktionieren: @RunWith(RetryRunner.class) public class TestSample { private static int i = 0; @AfterClass public static void testBefore() { System.out.println(“Vor dem Test”); i++; if(i < 2) {fail("Fail"); } } }

    – Benutzer1459144

    3. Dezember 14 um 10:39 Uhr

Wie kann ich fehlgeschlagene JUnit Tests sofort erneut ausfuhren
Sergii

Der vorgeschlagene Kommentar wurde basierend auf ob geschrieben Dies Artikel mit einigen Ergänzungen.

Wenn hier ein Testfall aus Ihrem jUnit-Projekt das Ergebnis „Fehler“ oder „Fehler“ erhält, wird dieser Testfall noch einmal ausgeführt. Insgesamt stellen wir hier 3 Chancen ein, um ein Erfolgsergebnis zu erzielen.

Also müssen wir Regelklasse erstellen und fügen Sie “@Rule”-Benachrichtigungen zu Ihrer Testklasse hinzu.

Wenn Sie nicht für jede Ihrer Testklassen die gleichen “@Rule”-Benachrichtigungen schreiben möchten, können Sie sie zu Ihrer abstrakten SetProperty-Klasse (falls vorhanden) und Erweiterungen hinzufügen.

Regelklasse:

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

Testklasse:

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

Sie müssen Ihre eigenen schreiben org.junit.runner.Runner und kommentieren Sie Ihre Tests mit @RunWith(YourRunner.class).

1644070090 989 Wie kann ich fehlgeschlagene JUnit Tests sofort erneut ausfuhren
DanD

Diese Antwort baut auf dieser Antwort auf.

Wenn Sie Ihre brauchen ActivityScenario (und Ihre Aktivität) vor jedem Lauf neu erstellt werden soll, können Sie sie mit try-with-resources starten. Der ActivityScenario wird dann nach jedem Versuch automatisch geschlossen.

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

Sie können dann in Ihren Tests über das auf Ihr Szenario zugreifen getScenario() Methode.

.

784020cookie-checkWie kann ich fehlgeschlagene JUnit-Tests sofort erneut ausführen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy