Einfache Möglichkeit, denselben Junit-Test immer und immer wieder durchzuführen?

121

Wie der Titel schon sagt, suche ich nach einer einfachen Möglichkeit, JUnit 4.x-Tests mehrmals hintereinander automatisch mit Eclipse auszuführen.

Ein Beispiel wäre, denselben Test zehnmal hintereinander auszuführen und das Ergebnis zurückzumelden.

Wir haben bereits eine komplexe Methode, aber ich suche nach einer einfachen Methode, damit ich sicher sein kann, dass der schuppige Test, den ich zu beheben versucht habe, behoben bleibt.

Eine ideale Lösung wäre ein Eclipse-Plugin / -Einstellung / -Funktion, das mir nicht bekannt ist.

Stefan Thyberg
quelle
5
Ich bin sehr gespannt, warum Sie das tun möchten.
Buhb
Ich führe einen großen Black-Box-Test durch, habe eine kleine Änderung vorgenommen und möchte sehen, wie sich dies auf die Stabilität dieses zuvor schuppigen Tests auswirkt.
Stefan Thyberg
Es ist in der Tat so, außer dass Sie möchten, dass es bis zum Fehler ausgeführt wird, während ich es nur einige Male ausführen möchte, was sich auf die Antworten auswirken kann, die ich erhalte.
Stefan Thyberg
4
Sind Sie gegen TestNG, denn wenn nicht, könnten Sie einfach @Test (invocationCount = 10) verwenden und das ist alles, was dazu gehört.
Robert Massaioli
1
Ich war nicht "gegen" TestNG, wir haben es einfach nicht in diesem Projekt verwendet.
Stefan Thyberg

Antworten:

123

Der einfachste Weg (da nur die geringste Menge an neuem Code erforderlich ist) besteht darin, den Test als parametrisierten Test auszuführen (mit einem zu kommentieren @RunWith(Parameterized.class)und eine Methode hinzuzufügen, um 10 leere Parameter bereitzustellen). Auf diese Weise führt das Framework den Test zehnmal aus.

Dieser Test muss der einzige Test in der Klasse sein, oder besser gesagt, alle Testmethoden müssen zehnmal in der Klasse ausgeführt werden.

Hier ist ein Beispiel:

@RunWith(Parameterized.class)
public class RunTenTimes {

    @Parameterized.Parameters
    public static Object[][] data() {
        return new Object[10][0];
    }

    public RunTenTimes() {
    }

    @Test
    public void runsTenTimes() {
        System.out.println("run");
    }
}

Mit dem oben Gesagten ist es sogar möglich, dies mit einem parameterlosen Konstruktor zu tun, aber ich bin mir nicht sicher, ob die Framework-Autoren dies beabsichtigt haben oder ob dies in Zukunft nicht mehr funktioniert.

Wenn Sie Ihren eigenen Läufer implementieren, kann der Läufer den Test zehnmal ausführen. Wenn Sie einen Runner eines Drittanbieters verwenden, können Sie mit 4.7 die neue @RuleAnnotation verwenden und die MethodRuleSchnittstelle so implementieren , dass sie die Anweisung aufnimmt und sie zehnmal in einer for-Schleife ausführt. Der aktuelle Nachteil dieses Ansatzes ist , dass @Beforeund @Aftererhält nur einmal ausgeführt. Dies wird sich wahrscheinlich in der nächsten Version von JUnit ändern (die @Beforewird nach der ausgeführt @Rule), aber unabhängig davon, ob Sie auf dieselbe Instanz des Objekts reagieren (etwas, das für den ParameterizedLäufer nicht zutrifft ). Dies setzt voraus, dass jeder Läufer, mit dem Sie die Klasse ausführen, die @RuleAnmerkungen korrekt erkennt . Dies ist nur dann der Fall, wenn es an die JUnit-Läufer delegiert.

Wenn Sie mit einem benutzerdefinierten Runner arbeiten, der die @RuleAnmerkung nicht erkennt , müssen Sie wirklich Ihren eigenen Runner schreiben, der entsprechend an diesen Runner delegiert und ihn zehnmal ausführt.

Beachten Sie, dass es andere Möglichkeiten gibt, dies möglicherweise zu lösen (z. B. den Theories-Läufer), für die jedoch alle ein Läufer erforderlich ist. Leider unterstützt JUnit derzeit keine Schichten von Läufern. Das ist ein Läufer, der andere Läufer kettet.

Yishai
quelle
2
Leider starte ich @RunWith bereits mit einem anderen Läufer, aber sonst wäre dies eine ideale Lösung gewesen.
Stefan Thyberg
Ja, dies ist die Lösung, die ich gerne hätte und die für die meisten Menschen am besten ist, also werde ich die Antwort akzeptieren.
Stefan Thyberg
Eine alternative und möglicherweise weniger hackige Lösung finden Sie unter: stackoverflow.com/a/21349010/281545
Mr_and_Mrs_D
Schöne Lösung! Ich habe eine Ausnahme erhalten, die mir mitteilt, dass die Datenmethode eine Iterable von Arrays zurückgeben soll. Ich habe es entsprechend behoben: @ Parameterized.Parameters public static Iterable <Object []> data () {return Arrays.asList (neues Objekt [20] [0]); }
Nadre
1
Könnten Sie bitte auf diese Antwort für JUnit 5 verlinken ? Es beschreibt die angeforderte Funktion, die in JUnit 5
Marcono1234
100

Mit IntelliJ können Sie dies über die Testkonfiguration tun. Sobald Sie dieses Fenster geöffnet haben, können Sie den Test beliebig oft ausführen.

Geben Sie hier die Bildbeschreibung ein

Wenn Sie den Test ausführen, führt Intellij alle von Ihnen ausgewählten Tests so oft aus, wie Sie es angegeben haben.

Beispiel für 10-maliges Ausführen von 624 Tests: Geben Sie hier die Bildbeschreibung ein

smac89
quelle
4
Das ist perfekt, wenn Sie jetzt auf eine Eclipse-Methode hinweisen können, würde dies die Frage des OP auf den Punkt bringen
khal
Sich auf ein bestimmtes Tool zu verlassen, um die tatsächliche Logik oder Anforderungen zu hosten, ist ein Anti-Pattern.
Mickael
1
@Mickael Das N-malige Wiederholen eines Tests ist normalerweise keine Voraussetzung für das Testen. Tatsächlich sollten Tests deterministisch sein, damit unabhängig davon, wie oft sie wiederholt werden, immer das gleiche Ergebnis erzielt wird. Können Sie das Anti-Muster erklären, von dem Sie sprechen?
smac89
Wenn das Wiederholen eines Tests für einen Entwickler nützlich war, ist es wahrscheinlich für andere nützlich. Wenn also die Testlaufzeit und der Code die Logik hosten können, um Wiederholungen zu ermöglichen, sollte dies bevorzugt werden, da dies den Faktor und die Lösung faktorisieren und es den Mitwirkenden ermöglichen kann, das gewünschte Tool mit demselben Ergebnis zu verwenden. Das Einfügen von wiederverwendbarer Logik in den IDE- / Entwicklerbereich, wenn sie in den Code eingefügt werden kann, ist eine Art fehlende Faktorisierung.
Mickael
68

Ich habe festgestellt, dass die wiederholte Anmerkung von Spring für solche Dinge nützlich ist:

@Repeat(value = 10)

Neuestes Dokument (Spring Framework 4.3.11.RELEASE API):

Laura
quelle
46
Das Ändern von Test-Frameworks ist keine einfache Möglichkeit, dies zu tun.
Stefan Thyberg
3
Sie müssen Ihr Testframework nicht ändern - es funktioniert gut mit JUnit. Der Hauptnachteil ist, dass JUnit es immer noch als einen einzelnen Test betrachtet. Wenn es zum ersten Mal unterbrochen wird, wird die Ausführung gestoppt. Wenn Sie Spring jedoch noch nicht verwenden, ist dies wahrscheinlich nicht der
richtige
Scheint bei mir nicht zu funktionieren (Spring 4.3.6 via Spring Boot 1.5.1)
David Tonhofer
Funktioniert nicht für mich mit Spring Boot 2.1.6 und 5.
Juni
Funktioniert perfekt mit Spring Boot 2. Vergessen Sie nicht, @RunWith (SpringRunner :: class) gemäß dem Link "Unit Testing in Spring" des Posters hinzuzufügen!
Agoston Horvath
33

Inspiriert von folgenden Ressourcen:

Beispiel

Erstellen und verwenden Sie eine @RepeatAnmerkung wie folgt:

public class MyTestClass {

    @Rule
    public RepeatRule repeatRule = new RepeatRule();

    @Test
    @Repeat(10)
    public void testMyCode() {
        //your test code goes here
    }
}

Repeat.java

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention( RetentionPolicy.RUNTIME )
@Target({ METHOD, ANNOTATION_TYPE })
public @interface Repeat {
    int value() default 1;
}

RepeatRule.java

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

public class RepeatRule implements TestRule {

    private static class RepeatStatement extends Statement {
        private final Statement statement;
        private final int repeat;    

        public RepeatStatement(Statement statement, int repeat) {
            this.statement = statement;
            this.repeat = repeat;
        }

        @Override
        public void evaluate() throws Throwable {
            for (int i = 0; i < repeat; i++) {
                statement.evaluate();
            }
        }

    }

    @Override
    public Statement apply(Statement statement, Description description) {
        Statement result = statement;
        Repeat repeat = description.getAnnotation(Repeat.class);
        if (repeat != null) {
            int times = repeat.value();
            result = new RepeatStatement(statement, times);
        }
        return result;
    }
}

PowerMock

Die Verwendung dieser Lösung mit @RunWith(PowerMockRunner.class)erfordert ein Update auf Powermock 1.6.5 (das einen Patch enthält ).

R. Oosterholt
quelle
Ja. Wie führen Sie die Tests durch?
R. Oosterholt
Ich benutze Eclipse selbst nicht. Vielleicht verwenden Sie keinen Junut 4-Testläufer? (siehe Dokument "Anpassen einer Testkonfiguration" )
R. Oosterholt
29

Mit JUnit 5 konnte ich dies mithilfe der Annotation @RepeatedTest lösen :

@RepeatedTest(10)
public void testMyCode() {
    //your test code goes here
}

Beachten Sie, dass @TestAnmerkungen nicht zusammen mit verwendet werden sollten @RepeatedTest.

César Alberca
quelle
Klingt sehr vielversprechend, nur um zu beachten, dass es noch keine Release-Version gibt.
9ilsdx 9rvj 0lo
1
JUnit 5 hat GA vor ein paar Wochen getroffen.
mkobit
11

Irgendwas falsch mit:

@Test
void itWorks() {
    // stuff
}

@Test
void itWorksRepeatably() {
    for (int i = 0; i < 10; i++) {
        itWorks();
    }
}

Im Gegensatz zu dem Fall, in dem Sie jedes einzelne Array von Werten testen, ist es Ihnen egal, welcher Lauf fehlgeschlagen ist.

Sie müssen in der Konfiguration oder in den Anmerkungen nicht tun, was Sie im Code tun können.

soru
quelle
2
Ich möchte mehrere Tests als normale Komponententests durchführen und für jeden eine Ablaufverfolgung und einen Status erhalten.
Stefan Thyberg
24
In diesem Fall werden "@Before" und "@After" nicht ausgeführt
Bogdan
3
Dies zusammen mit dem manuellen Aufrufen der @Beforekommentierten Methode, bevor itWorks() mein Problem gelöst wurde.
João Neves
Kennen Sie das DRY-Konzept? en.wikipedia.org/wiki/Don%27t_repeat_yourself Ich empfehle, einige Einstellungen vorzunehmen, anstatt Ihre Schleife überall einzufügen.
Kikiwa
Die Bearbeitungswarteschlange für diese Antwort ist voll. Deshalb werde ich es in einen Kommentar setzen: Für JUnit4 müssen die Tests öffentlich sein.
Richard Jessop
7

Das funktioniert für mich viel einfacher.

public class RepeatTests extends TestCase {

    public static Test suite() {
        TestSuite suite = new TestSuite(RepeatTests.class.getName());

        for (int i = 0; i < 10; i++) {              
        suite.addTestSuite(YourTest.class);             
        }

        return suite;
    }
}
Qualk
quelle
Genial, da kein anderes Framework verwendet wird und tatsächlich mit JUnit 3 (entscheidend für Android) funktioniert
Vladimir Ivanov
1
Eine Implementierung mit JUnit4 könnte mit einem Runner erfolgen: public class RepeatRunner extends BlockJUnit4ClassRunner { public RepeatRunner(Class klass) throws InitializationError { super(klass); } @Override public void run(final RunNotifier notifier) { for (int i = 0; i < 10; i++) { super.run(notifier); } } }Obwohl zumindest im Eclipse JUnit-Plugin folgende Ergebnisse angezeigt werden : "10/1 Tests bestanden"
Peter Wippermann
7

Es gibt eine intermittierende Anmerkung in der Tempus-Fugit- Bibliothek, die mit JUnit 4.7 zusammenarbeitet @Rule, um einen Test mehrmals oder mit zu wiederholen @RunWith.

Beispielsweise,

@RunWith(IntermittentTestRunner.class)
public class IntermittentTestRunnerTest {

   private static int testCounter = 0;

   @Test
   @Intermittent(repition = 99)
   public void annotatedTest() {
      testCounter++;
   }
}

Nach dem Ausführen des Tests (mit dem IntermittentTestRunner im @RunWith) testCounterwäre gleich 99.

Toby
quelle
Ja, hier ist das gleiche Problem, da bereits ein anderer Läufer verwendet wird und dieser daher nicht verwendet werden kann. Eine gute Idee.
Stefan Thyberg
Ja, ich habe das gleiche Problem mit RunWith ... im Laufe der Zeit habe ich Tempus-Fugit optimiert, um es ein wenig zu umgehen. Sie können eine @Rule anstelle eines Läufers verwenden, wenn Sie wiederholt ausführen möchten. Sie markieren es mit @Repeating anstatt intermittierend. Die Regelversion wird jedoch nicht @ Before / @ Afters ausgeführt. Weitere Informationen finden Sie unter tempus-fugit.googlecode.com/svn/site/documentation/… (zum Laden / Einweichen des Tests nach unten scrollen).
Toby
0

Ich baue ein Modul, das diese Art von Tests ermöglicht. Es wird aber nicht nur wiederholt fokussiert. Aber in der Garantie, dass ein Teil des Codes Thread-sicher ist.

https://github.com/anderson-marques/concurrent-testing

Maven-Abhängigkeit:

<dependency>
    <groupId>org.lite</groupId>
    <artifactId>concurrent-testing</artifactId>
    <version>1.0.0</version>
</dependency>

Anwendungsbeispiel:

package org.lite.concurrent.testing;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import ConcurrentTest;
import ConcurrentTestsRule;

/**
 * Concurrent tests examples
 */
public class ExampleTest {

    /**
     * Create a new TestRule that will be applied to all tests
     */
    @Rule
    public ConcurrentTestsRule ct = ConcurrentTestsRule.silentTests();

    /**
     * Tests using 10 threads and make 20 requests. This means until 10 simultaneous requests.
     */
    @Test
    @ConcurrentTest(requests = 20, threads = 10)
    public void testConcurrentExecutionSuccess(){
        Assert.assertTrue(true);
    }

    /**
     * Tests using 10 threads and make 20 requests. This means until 10 simultaneous requests.
     */
    @Test
    @ConcurrentTest(requests = 200, threads = 10, timeoutMillis = 100)
    public void testConcurrentExecutionSuccessWaitOnly100Millissecond(){
    }

    @Test(expected = RuntimeException.class)
    @ConcurrentTest(requests = 3)
    public void testConcurrentExecutionFail(){
        throw new RuntimeException("Fail");
    }
}

Dies ist ein Open Source-Projekt. Fühlen Sie sich frei zu verbessern.

Anderson Marques
quelle
0

Sie können Ihren JUnit-Test mit einer Hauptmethode ausführen und ihn so oft wiederholen, bis Sie Folgendes benötigen:

package tests;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.Result;

public class RepeatedTest {

    @Test
    public void test() {
        fail("Not yet implemented");
    }

    public static void main(String args[]) {

        boolean runForever = true;

        while (runForever) {
            Result result = org.junit.runner.JUnitCore.runClasses(RepeatedTest.class);

            if (result.getFailureCount() > 0) {
                runForever = false;
               //Do something with the result object

            }
        }

    }

}
silber_mx
quelle
0

Dies ist im Wesentlichen die Antwort, die Yishai oben in Kotlin neu geschrieben hat:

@RunWith(Parameterized::class)
class MyTest {

    companion object {

        private const val numberOfTests = 200

        @JvmStatic
        @Parameterized.Parameters
        fun data(): Array<Array<Any?>> = Array(numberOfTests) { arrayOfNulls<Any?>(0) }
    }

    @Test
    fun testSomething() { }
}
Kennzeichen
quelle