Gleichzeitiger JUnit-Test

72

Ich habe eine große JUnit-Testsuite, in der ich aus zwei Gründen alle Tests gleichzeitig ausführen möchte:

  • Nutzen Sie mehrere Kerne, um die gesamte Testsuite schneller auszuführen
  • Hoffentlich werden einige Fehler aufgrund nicht threadsicherer globaler Objekte erkannt

Ich erkenne, dass dies mich zwingen wird, Code umzugestalten, um ihn threadsicher zu machen, aber ich halte das für eine gute Sache :-)

Was ist der beste Weg, um JUnit dazu zu bringen, alle Tests gleichzeitig auszuführen?

mikera
quelle

Antworten:

31

Sind Sie an JUnit gebunden? TestNG bietet sofort gute Multithread-Tests und ist mit JUnit-Tests kompatibel (Sie müssen einige Änderungen vornehmen). Zum Beispiel könnten Sie einen Test wie folgt ausführen:

@Test(threadPoolSize = 3, invocationCount = 9,  timeOut = 10000)
public void doSomething() {
...
}

Dies würde bedeuten, dass die doSomething()Methode 9 Mal von 3 verschiedenen Threads aufgerufen wird.

Ich kann TestNG nur empfehlen .

Chandradeep
quelle
+1 für TestNG, ich verwende es für alle meine Thread-Sicherheitstests. Es hat auch schöne parametrisierte Tests.
25
Herabgestuft, weil die OP-Frage nicht beantwortet wurde. Er gibt ausdrücklich an, dass er seine gesamte Suite über Threads ausführen und eine einzelne Methode mehrmals
ausführen möchte
7
@TobyHobson Dies ist eine schreckliche Methode zum Testen der Thread-Sicherheit. Und per Definition keine Einheitentests, da sie nicht deterministisch sind. Und TestNG enthält zahlreiche Fehler. Ich habe einige von ihnen in letzter Zeit repariert, aber sie werden nicht gezogen (kein Vorschlag), TestNG ist ziemlich inaktiv. Versuchen Sie lieber Thread Weaver ( code.google.com/p/thread-weaver )
Rafael Winterhalter
22

Ich habe nach einer Antwort auf genau diese Frage gesucht, und basierend auf den Antworten hier und dem, was ich an anderer Stelle gelesen habe, scheint es derzeit keine einfache Möglichkeit zu geben, vorhandene Tests parallel mit auszuführen JUnit. Oder wenn ja, habe ich es nicht gefunden. Also habe ich einen einfachen JUnit Runner geschrieben, der das schafft. Bitte zögern Sie nicht, es zu benutzen; Eine vollständige Erklärung und den Quellcode der MultiThreadedRunner-Klasse finden Sie unter http://falutin.net/2012/12/30/multithreaded-testing-with-junit/ . Mit dieser Klasse können Sie Ihre vorhandenen Testklassen wie folgt kommentieren:

@RunWith(MultiThreadedRunner.class)
Mike Sokolov
quelle
Haben Sie eine Idee, wie Sie einen gleichzeitigen Suite-Läufer und nicht nur einen gleichzeitigen Testläufer implementieren können? Könnte auch hilfreich sein: p
Stéphane Piette
Das habe ich mir nicht angesehen, Stéphane. Aber den Testläufer zu machen war so einfach, ich wette, der Suite-Läufer wäre auch nicht schwer. Wenn Sie das Problem lösen, werde ich gerne Ihren Code zu meinem Beitrag hinzufügen :)
Mike Sokolov
Der Link ist tot.
Gehirn
Es lebt wieder!
Mike Sokolov
22

Der folgende Code sollte Ihre Anforderungen erfüllen, der aus dem deutschen Buch JUnit Profiwissen stammt, das einige Hinweise zum parallelen Testen von Inhalten oder zur Reduzierung der Ausführungszeit durch Verwendung mehrerer Kerne anstelle nur eines einzigen Kerns enthält.

JUnit 4.6 führte eine ParallelComputer-Klasse ein, die die parallele Ausführung von Tests bot. Diese Funktionalität war jedoch erst mit JUnit 4.7 öffentlich zugänglich, wodurch die Möglichkeit bestand, einen benutzerdefinierten Scheduler für den übergeordneten Runner festzulegen.

public class ParallelScheduler implements RunnerScheduler {

    private ExecutorService threadPool = Executors.newFixedThreadPool(
        Runtime.getRuntime().availableProcessors());

    @Override
    public void schedule(Runnable childStatement) {
        threadPool.submit(childStatement);
    }

    @Override
    public void finished() {
        try {
            threadPool.shutdown();
            threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Got interrupted", e);
        }
    }
}

public class ParallelRunner extends BlockJUnit4ClassRunner {

    public ParallelRunner(Class<?> klass) throws InitializationError {
        super(klass);
        setScheduler(new ParallelScheduler());
    }
}

Wenn Sie jetzt eine Testklasse mit Anmerkungen versehen @RunWith(ParallelRunner.class), wird jede Methode in einem eigenen Thread ausgeführt. Darüber hinaus gibt es (nur) so viele aktive Threads, wie CPU-Kerne auf dem ausführenden Computer verfügbar sind.

Wenn mehrere Klassen gleichzeitig ausgeführt werden sollen, können Sie eine angepasste Suite wie folgt definieren:

public class ParallelSuite extends Suite {

    public ParallelSuite(Class<?> klass, RunnerBuilder builder) 
      throws InitializationError {
        super(klass, builder);
        setScheduler(new ParallelScheduler());
    }
}

und dann ändern @RunWith(Suite.class)mit@RunWith(ParallelSuite.class)

Sie können sogar die Funktionalität von zB nutzen, WildcardPatternSuiteindem Sie direkt von dieser Suite aus erweitern, anstatt Suitewie im vorherigen Beispiel. Auf diese Weise können Sie außerdem Unit-Tests nach Belieben filtern @Category- eine TestSuite, die nur UnitTestkommentierte Kategorien parallel ausführt, könnte folgendermaßen aussehen:

public interface UnitTest {

}

@RunWith(ParallelSuite.class)
@SuiteClasses("**/*Test.class")
@IncludeCategories(UnitTest.class)
public class UnitTestSuite {

}

Ein einfacher Testfall könnte nun so aussehen:

@Category(UnitTest.class)
@RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {

    @Test
    public void testSomething() {
        ...
    }
}

Das UnitTestSuiteführt jede Klasse aus, die in Unterverzeichnissen gefunden wird, die mit Testeiner @Category(UnitTest.class)parallel angegebenen Klasse endet und diese hat - abhängig von der Anzahl der verfügbaren CPU-Kerne.

Ich bin mir nicht sicher, ob es einfacher werden kann :)

Roman Vottner
quelle
6
Sie müssen diesen Code nicht neu schreiben. Der Autor veröffentlichte eine Bibliothek namens JUnit Toolbox: code.google.com/p/junit-toolbox
Stefan Birkner
6

Anscheinend hat Mathieu Carbou in seinem Beitrag "Concurrent JUnit Tests With RunnerScheduler" eine Implementierung für Concurrence durchgeführt, die helfen könnte!

http://dzone.com/articles/concurrent-junit-tests

OneJunit

@RunWith(ConcurrentJunitRunner.class)
@Concurrent(threads = 6)
public final class ATest {
 
    @Test public void test0() throws Throwable { printAndWait(); }
    @Test public void test1() throws Throwable { printAndWait(); }
    @Test public void test2() throws Throwable { printAndWait(); }
    @Test public void test3() throws Throwable { printAndWait(); }
    @Test public void test4() throws Throwable { printAndWait(); }
    @Test public void test5() throws Throwable { printAndWait(); }
    @Test public void test6() throws Throwable { printAndWait(); }
    @Test public void test7() throws Throwable { printAndWait(); }
    @Test public void test8() throws Throwable { printAndWait(); }
    @Test public void test9() throws Throwable { printAndWait(); }

    void printAndWait() throws Throwable {
        int w = new Random().nextInt(1000);
        System.out.println(String.format("[%s] %s %s %s",Thread.currentThread().getName(), getClass().getName(), new Throwable       ().getStackTrace()[1].getMethodName(), w));
        Thread.sleep(w);
    }
}

Mehrere JUnits:

@RunWith(ConcurrentSuite.class)
@Suite.SuiteClasses({ATest.class, ATest2.class, ATest3.class})
public class MySuite {
}
molavec
quelle
1
Dies ist ziemlich nett, scheint aber nicht mit Tests zu funktionieren, die eine @ BeforeClass- und eine @ Before-Methode enthalten. Es wird versucht, die @ BeforeClass-Methoden für jeden Thread anstatt nur einmal auszuführen. Für unser Setup bedeutet dies, dass 5 Threads gleichzeitig die DB
löschen und neu erstellen
@Splaktar Eine Möglichkeit, dies zu lösen, besteht darin, einen statischen Verweis auf eine AtomicBoolean isInitializedVariable in der Testklasse beizubehalten, der festgelegt wird, nachdem die Methode before class einmal ausgeführt wurde. Alle zusätzlichen Aufrufe der init-Methode werden nur zurückgegeben, wenn diese boolesche Variable wahr ist. Dies verhindert, dass mehrere Threads bereits initialisierte Daten
löschen
2

Sie können auch HavaRunner ausprobieren . Es ist ein JUnit-Runner, der standardmäßig Tests parallel ausführt.

HavaRunner verfügt auch über praktische Suiten: Sie können einen Test als Mitglied einer Suite deklarieren, indem Sie @PartOf(YourIntegrationTestSuite.class)der Klasse die Anmerkung hinzufügen . Dieser Ansatz unterscheidet sich von dem von JUnit, bei dem Sie die Suite-Mitgliedschaften in der Suite-Klasse deklarieren.

Darüber hinaus können HavaRunner-Suiten schwergewichtige Objekte wie einen eingebetteten Webanwendungscontainer initialisieren. HavaRunner übergibt dieses schwere Objekt dann an den Konstruktor jedes Suite-Mitglieds. Dies beseitigt die Notwendigkeit von @BeforeClassund @AfterClassAnmerkungen, die problematisch sind, weil sie den globalen veränderlichen Zustand fördern, was wiederum die Parallelisierung schwierig macht.

Schließlich hat HavaRunner Szenarien - eine Möglichkeit, denselben Test für verschiedene Daten auszuführen. Szenarien reduzieren die Notwendigkeit, Testcode zu duplizieren.

HavaRunner wurde in zwei mittelgroßen Java-Projekten im Kampf getestet.

Ps. Ich bin der Autor von HavaRunner und würde mich über Ihr Feedback freuen.

Lauri Lehmijoki
quelle