Test simultanei di JUnit

Ho una grande suite di test JUnit, dove mi piacerebbe eseguire tutti i test contemporaneamente per due motivi:

  • Sfrutta più core per eseguire l’intera suite di test più velocemente
  • Speriamo di rilevare alcuni errori dovuti a oggetti globali non thread-safe

Riconosco che questo mi costringerà a refactoring del codice per renderlo thread-safe, ma ritengo che sia una buona cosa 🙂

Qual è il modo migliore per fare in modo che JUnit esegua tutti i test contemporaneamente?

Sei fissato a JUnit? TestNG offre una buona prova multi-thread pronta all’uso ed è compatibile con i test JUnit (è necessario apportare alcune modifiche). Ad esempio potresti eseguire un test come questo:

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

Ciò significherebbe che il metodo doSomething() verrà invocato 9 volte da 3 thread differenti.

Consiglio vivamente TestNG .

Stavo cercando una risposta esattamente a questa domanda, e sulla base delle risposte qui e di ciò che ho letto altrove, sembra che al momento non esista un modo semplice e immediato per eseguire test esistenti in parallelo usando JUnit. O se c’è non l’ho trovato. Così ho scritto un semplice JUnit Runner che lo realizza. Sentiti libero di utilizzarlo; vedi http://falutin.net/2012/12/30/multithreaded-testing-with-junit/ per una spiegazione completa e il codice sorgente della class MultiThreadedRunner. Con questa class, puoi semplicemente annotare le classi di test esistenti in questo modo:

 @RunWith(MultiThreadedRunner.class) 

Il seguente codice dovrebbe raggiungere i tuoi requisiti, che è stato preso dal libro tedesco JUnit Profiwissen che contiene alcuni suggerimenti su entrambi i test in parallelo o su come ridurre i tempi di esecuzione attraverso l’uso di più core invece di un solo core.

JUnit 4.6 ha introdotto una class ParallelComputer che offriva l’esecuzione parallela dei test. Tuttavia, questa funzionalità non era accessibile pubblicamente fino a JUnit 4.7 che offriva la possibilità di impostare uno scheduler personalizzato per il genitore.

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

Se ora @RunWith(ParallelRunner.class) una class di prova con @RunWith(ParallelRunner.class) ogni metodo verrà eseguito all’interno del proprio thread. Inoltre, ci saranno tanti thread attivi (solo) poiché i core della CPU sono disponibili sulla macchina in esecuzione.

Se più classi dovrebbero essere eseguite in parallelo, puoi definire una suite personalizzata come questa:

 public class ParallelSuite extends Suite { public ParallelSuite(Class klass, RunnerBuilder builder) throws InitializationError { super(klass, builder); setScheduler(new ParallelScheduler()); } } 

e poi cambia @RunWith(Suite.class) con @RunWith(ParallelSuite.class)

Puoi anche sfruttare la funzionalità di WildcardPatternSuite estendendo direttamente da quella suite invece di Suite come nell’esempio precedente. Ciò ti consente inoltre di filtrare i test di unità di qualsiasi @Category – un TestSuite che esegue solo le categorie annotate di UnitTest in parallelo potrebbe assomigliare a questo:

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

Un semplice caso di test potrebbe ora assomigliare a questo:

 @Category(UnitTest.class) @RunWith(MockitoJUnitRunner.class) public class SomeClassTest { @Test public void testSomething() { ... } } 

UnitTestSuite eseguirà ogni class trovata nelle sottodirectory che termina con Test e ha una @Category(UnitTest.class) specificata in parallelo, a seconda del numero di core della CPU disponibili.

Non sono sicuro che possa essere più semplice di così 🙂

Apparentemente Mathieu Carbou ha fatto l’implementazione per la concorrenza che potrebbe aiutare!

http://java.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); } } 

JUnit multipli:

 @RunWith(ConcurrentSuite.class) @Suite.SuiteClasses({ATest.class, ATest2.class, ATest3.class}) public class MySuite { } 

Puoi anche provare HavaRunner . È un runner JUnit che esegue test in parallelo per impostazione predefinita.

HavaRunner ha anche suite a portata di mano: puoi dichiarare un test come membro di una suite aggiungendo l’annotazione @PartOf(YourIntegrationTestSuite.class) alla class. Questo approccio differisce da JUnit, dove dichiarate le appartenenze alla suite nella class suite.

Inoltre, le suite HavaRunner possono intializzare oggetti pesanti come un contenitore di applicazioni Web incorporato. HavaRunner passa quindi questo object pesante al costruttore di ciascun membro della suite. Ciò elimina la necessità delle annotazioni @BeforeClass e @AfterClass , che sono problematiche, perché promuovono lo stato globale mutabile, il che a sua volta rende difficile la parallelizzazione.

Infine, HavaRunner ha degli scenari: un modo per eseguire lo stesso test con dati diversi. Gli scenari riducono la necessità di duplicare il codice di test.

HavaRunner è stato testato in battaglia in due progetti Java di medie dimensioni.

Ps. Sono l’autore di HavaRunner e apprezzerei il tuo feedback su di esso.