Ausführen von JUnit-Tests parallel zu Maven

1. Einleitung

Obwohl das serielle Ausführen von Tests die meiste Zeit einwandfrei funktioniert, möchten wir sie möglicherweise parallelisieren, um die Arbeit zu beschleunigen.

In diesem Tutorial erfahren Sie, wie Sie Tests mit dem Surefire-Plugin von JUnit und Maven parallelisieren. Zuerst führen wir alle Tests in einem einzigen JVM-Prozess aus, dann versuchen wir es mit einem Projekt mit mehreren Modulen.

2. Maven-Abhängigkeiten

Beginnen wir mit dem Importieren der erforderlichen Abhängigkeiten. Wir müssen JUnit 4.7 oder höher zusammen mit Surefire 2.16 oder höher verwenden:

 junit junit 4.12 test 
 org.apache.maven.plugins maven-surefire-plugin 2.22.0 

Kurz gesagt, Surefire bietet zwei Möglichkeiten, um Tests parallel auszuführen:

  • Multithreading in einem einzelnen JVM-Prozess
  • Forking mehrerer JVM-Prozesse

3. Parallele Tests ausführen

Um einen Test parallel auszuführen, sollten wir einen Testläufer verwenden, der org.junit.runners.ParentRunner erweitert .

Selbst Tests, die keinen expliziten Testläufer deklarieren, funktionieren jedoch, da der Standardläufer diese Klasse erweitert.

Um die parallele Testausführung zu demonstrieren, verwenden wir als Nächstes eine Testsuite mit zwei Testklassen mit jeweils einigen Methoden. Tatsächlich würde jede Standardimplementierung einer JUnit-Testsuite ausreichen.

3.1. Parallele Parameter verwenden

Lassen Sie uns zunächst das parallele Verhalten in Surefire mithilfe des parallelen Parameters aktivieren . Es gibt den Grad der Granularität an, auf dem wir Parallelität anwenden möchten.

Die möglichen Werte sind:

  • Methoden - Führt Testmethoden in separaten Threads aus
  • Klassen - Führt Testklassen in separaten Threads aus
  • classesAndMethods - Führt Klassen und Methoden in separaten Threads aus
  • Suiten - Läuft Suiten parallel
  • suitesAndClasses - Führt Suites und Klassen in separaten Threads aus
  • suitesAndMethods - Erstellt separate Threads für Klassen und Methoden
  • all - führt Suites, Klassen sowie Methoden in separaten Threads aus

In unserem Beispiel verwenden wir alle :

 all 

Zweitens definieren wir die Gesamtzahl der Threads, die Surefire erstellen soll. Wir können das auf zwei Arten tun:

Mit threadCount , das die maximale Anzahl von Threads definiert, erstellt Surefire Folgendes :

10

Oder verwenden Sie den Parameter useUnlimitedThreads , bei dem ein Thread pro CPU-Kern erstellt wird:

true

Standardmäßig ist threadCount pro CPU-Kern. Wir können den Parameter perCoreThreadCount verwenden , um dieses Verhalten zu aktivieren oder zu deaktivieren:

true

3.2. Verwenden von Thread-Count-Einschränkungen

Angenommen, wir möchten die Anzahl der Threads definieren, die auf Methoden-, Klassen- und Suite-Ebene erstellt werden sollen. Wir können dies mit den Parametern threadCountMethods , threadCountClasses und threadCountSuites tun .

Kombinieren wir diese Parameter mit threadCount aus der vorherigen Konfiguration:

2 2 6

Da wir früher alle in parallel, haben wir die Fadenzahl für Methoden, Suiten definiert und Klassen. Es ist jedoch nicht zwingend erforderlich, den Blattparameter zu definieren. Surefire leitet die Anzahl der Threads ab, die verwendet werden sollen, wenn Blattparameter weggelassen werden.

Wenn beispielsweise threadCountMethods weggelassen wird, müssen wir nur sicherstellen, dass threadCount > threadCountClasses + threadCountSuites.

Manchmal möchten wir möglicherweise die Anzahl der für Klassen, Suites oder Methoden erstellten Threads begrenzen, selbst wenn wir eine unbegrenzte Anzahl von Threads verwenden.

In solchen Fällen können wir auch Einschränkungen der Threadanzahl anwenden:

true 2

3.3. Zeitüberschreitungen einstellen

Manchmal müssen wir möglicherweise sicherstellen, dass die Testausführung zeitlich begrenzt ist.

Dazu können wir den Parameter parallelTestTimeoutForcedInSeconds verwenden . Dies unterbricht derzeit laufende Threads und führt nach Ablauf des Timeouts keinen der in der Warteschlange befindlichen Threads aus:

5

Eine andere Option ist die Verwendung von parallelTestTimeoutInSeconds .

In diesem Fall wird nur die Ausführung der Threads in der Warteschlange gestoppt:

3.5

Bei beiden Optionen werden die Tests jedoch nach Ablauf des Zeitlimits mit einer Fehlermeldung beendet.

3.4. Vorsichtsmaßnahmen

Surefire ruft statische Methoden auf, die im übergeordneten Thread mit @Parameters , @BeforeClass und @AfterClass kommentiert sind . Stellen Sie daher sicher, dass Sie auf mögliche Speicherinkonsistenzen oder Rennbedingungen prüfen, bevor Sie Tests parallel ausführen.

Auch Tests, die den gemeinsamen Status mutieren, sind definitiv keine guten Kandidaten für eine parallele Ausführung.

4. Testausführung in Maven-Projekten mit mehreren Modulen

Bisher haben wir uns darauf konzentriert, Tests innerhalb eines Maven-Moduls parallel durchzuführen.

Angenommen, wir haben mehrere Module in einem Maven-Projekt. Da diese Module nacheinander aufgebaut sind, werden die Tests für jedes Modul auch nacheinander ausgeführt.

Wir können dieses Standardverhalten ändern, indem wir den Parameter -T von Maven verwenden, mit dem Module parallel erstellt werden . Dies kann auf zwei Arten erfolgen.

Wir können entweder die genaue Anzahl der Threads angeben, die beim Erstellen des Projekts verwendet werden sollen:

mvn -T 4 surefire:test

Oder verwenden Sie die tragbare Version und geben Sie die Anzahl der Threads an, die pro CPU-Kern erstellt werden sollen:

mvn -T 1C surefire:test

In beiden Fällen können wir Tests beschleunigen und Ausführungszeiten erstellen.

5. Forking JVMs

Bei der parallelen Testausführung über die Option parallel erfolgt die Parallelität innerhalb des JVM-Prozesses mithilfe von Threads .

Da Threads denselben Speicherplatz gemeinsam nutzen, kann dies hinsichtlich Speicher und Geschwindigkeit effizient sein. Es können jedoch unerwartete Rennbedingungen oder andere subtile Testfehler im Zusammenhang mit der Parallelität auftreten. Wie sich herausstellt, kann das Teilen des gleichen Speicherplatzes sowohl ein Segen als auch ein Fluch sein.

Um Parallelitätsprobleme auf Thread-Ebene zu vermeiden, bietet Surefire einen weiteren parallelen Testausführungsmodus: Forking und Parallelität auf Prozessebene . Die Idee von gegabelten Prozessen ist eigentlich recht einfach. Anstatt mehrere Threads zu erzeugen und die Testmethoden zwischen ihnen zu verteilen, erstellt safefire neue Prozesse und führt dieselbe Verteilung durch.

Da es keinen gemeinsamen Speicher zwischen verschiedenen Prozessen gibt, werden wir nicht unter diesen subtilen Parallelitätsfehlern leiden. Dies geht natürlich zu Lasten einer höheren Speichernutzung und einer etwas geringeren Geschwindigkeit.

Wie auch immer, zu ermöglichen , um gabelt, wir müssen nur die Verwendung forkCount Eigenschaft und auf jeden beliebigen positiven Wert gesetzt:

3

Hier erstellt todsicher höchstens drei Gabeln aus der JVM und führt die Tests in ihnen aus. Der Standardwert für forkCount ist eins. Dies bedeutet, dass das Maven-Surefire-Plugin einen neuen JVM-Prozess erstellt, um alle Tests in einem Maven-Modul auszuführen.

Die forkCount- Eigenschaft unterstützt dieselbe Syntax wie -T . Das heißt, wenn wir das C an den Wert anhängen , wird dieser Wert mit der Anzahl der verfügbaren CPU-Kerne in unserem System multipliziert. Zum Beispiel:

2.5C

Dann kann Surefire in einer Zwei-Kern-Maschine höchstens fünf Gabeln für die parallele Testausführung erstellen.

Standardmäßig verwendet Surefire die erstellten Gabeln für andere Tests . Wenn wir jedoch die Eigenschaft reuseForks auf false setzen , wird jede Verzweigung nach dem Ausführen einer Testklasse zerstört.

Um das Gabeln zu deaktivieren, können wir auch den forkCount auf Null setzen.

6. Fazit

Zusammenfassend haben wir zunächst das Multithread-Verhalten aktiviert und den Parallelitätsgrad mithilfe des parallelen Parameters definiert. Anschließend haben wir die Anzahl der Threads, die Surefire erstellen soll, begrenzt. Später legen wir Timeout-Parameter fest, um die Testausführungszeiten zu steuern.

Schließlich haben wir uns angesehen, wie wir die Ausführungszeiten für Builds reduzieren und daher die Ausführungszeiten in Maven-Projekten mit mehreren Modulen testen können.

Der hier vorgestellte Code ist wie immer auf GitHub verfügbar.