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?
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)
quelle
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,
WildcardPatternSuite
indem Sie direkt von dieser Suite aus erweitern, anstattSuite
wie im vorherigen Beispiel. Auf diese Weise können Sie außerdem Unit-Tests nach Belieben filtern@Category
- eine TestSuite, die nurUnitTest
kommentierte 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
UnitTestSuite
führt jede Klasse aus, die in Unterverzeichnissen gefunden wird, die mitTest
einer@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 :)
quelle
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 { }
quelle
AtomicBoolean isInitialized
Variable 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 DatenSie 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
@BeforeClass
und@AfterClass
Anmerkungen, 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.
quelle