Test JUnit simultané

J’ai une grande suite de tests JUnit, où je voudrais bien exécuter tous les tests simultanément pour deux raisons:

  • Exploitez plusieurs cœurs pour exécuter la suite de tests plus rapidement
  • Espérons détecter certaines erreurs dues à des objects globaux non-thread-safe

Je reconnais que cela va me forcer à refactoriser du code pour le rendre sûr du thread, mais je considère que c’est une bonne chose 🙂

Quelle est la meilleure façon d’obtenir que JUnit exécute tous les tests simultanément?

Êtes-vous fixé sur JUnit? TestNG fournit de bons tests multi-threads et est compatible avec les tests JUnit (vous devez apporter quelques modifications). Par exemple, vous pouvez exécuter un test comme celui-ci:

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

Cela signifierait que la méthode doSomething() sera invoquée 9 fois par 3 threads différents.

Je recommande fortement TestNG .

Je cherchais une réponse à cette question et, sur la base des réponses fournies ici et de ce que je lis ailleurs, il semble qu’il n’existe pas actuellement de méthode simple pour exécuter des tests existants en parallèle. JUnit. Ou s’il y en a je ne l’ai pas trouvé. J’ai donc écrit un simple JUnit Runner qui accomplit cela. N’hésitez pas à l’utiliser. voir http://falutin.net/2012/12/30/multithreaded-testing-with-junit/ pour une explication complète et le code source de la classe MultiThreadedRunner. Avec cette classe, vous pouvez simplement annoter vos classes de test existantes comme ceci:

 @RunWith(MultiThreadedRunner.class) 

Le code suivant doit répondre à vos exigences, extrait du livre allemand JUnit Profiwissen, qui contient des astuces pour tester des éléments en parallèle ou réduire le temps d’exécution en utilisant plusieurs cœurs au lieu d’un seul cœur.

JUnit 4.6 a introduit une classe ParallelComputer qui offre l’exécution en parallèle des tests. Cependant, cette fonctionnalité n’était pas accessible publiquement jusqu’à JUnit 4.7, qui offrait la possibilité de définir un planificateur personnalisé pour le coureur parent.

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

Si vous @RunWith(ParallelRunner.class) maintenant une classe de test avec @RunWith(ParallelRunner.class) chaque méthode s’exécutera dans son propre thread. De plus, il y aura autant de threads actifs (uniquement) que de processeurs disponibles sur la machine d’exécution.

Si plusieurs classes doivent être exécutées en parallèle, vous pouvez définir une suite personnalisée comme ceci:

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

puis modifiez @RunWith(Suite.class) avec @RunWith(ParallelSuite.class)

Vous pouvez même utiliser les fonctionnalités de la WildcardPatternSuite en étendant directement depuis cette suite au lieu de Suite comme dans l’exemple précédent. Cela vous permet en outre de filtrer les tests unitaires par n’importe quelle @Category – un TestSuite qui exécute UnitTest catégories annotées UnitTest en parallèle pourrait ressembler à ceci:

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

Un simple cas de test pourrait maintenant ressembler à ceci:

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

UnitTestSuite exécute chaque classe trouvée dans des sous-répertoires qui se terminent par Test et possède une @Category(UnitTest.class) spécifiée en parallèle – en fonction du nombre de cœurs de CPU disponibles.

Je ne suis pas sûr que cela puisse être plus simple que ça 🙂

Apparemment, Mathieu Carbou a mis en œuvre pour la concurrence qui pourrait aider!

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(Ssortingng.format("[%s] %s %s %s",Thread.currentThread().getName(), getClass().getName(), new Throwable ().getStackTrace()[1].getMethodName(), w)); Thread.sleep(w); } } 

Plusieurs unités J:

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

Vous pouvez également essayer HavaRunner . C’est un runner JUnit qui exécute des tests en parallèle par défaut.

HavaRunner possède également des suites pratiques: vous pouvez déclarer qu’un test est membre d’une suite en ajoutant l’annotation @PartOf(YourIntegrationTestSuite.class) à la classe. Cette approche diffère de celle de JUnit, où vous déclarez les appartenances à la suite dans la classe de la suite.

De plus, les suites HavaRunner peuvent intialiser des objects lourds tels qu’un conteneur d’application Web intégré. HavaRunner transmet ensuite cet object lourd au constructeur de chaque membre de la suite. Cela supprime la nécessité des annotations @BeforeClass et @AfterClass , qui posent problème, car elles favorisent un état mutable global, ce qui rend la parallélisation difficile.

Enfin, HavaRunner propose des scénarios – une manière d’exécuter le même test avec des données différentes. Les scénarios réduisent la nécessité de dupliquer le code de test.

HavaRunner a été testé dans deux projets Java de taille moyenne.

Ps. Je suis l’auteur de HavaRunner et j’apprécierais vos commentaires.