Exécution de tests JUnit en parallèle avec Maven

1. Introduction

Bien que l'exécution des tests en série fonctionne très bien la plupart du temps, nous souhaitons peut-être les paralléliser pour accélérer les choses.

Dans ce didacticiel, nous verrons comment paralléliser les tests à l'aide de JUnit et du plug-in Surefire de Maven. Tout d'abord, nous allons exécuter tous les tests dans un seul processus JVM, puis nous l'essayerons avec un projet multi-module.

2. Dépendances de Maven

Commençons par importer les dépendances requises. Nous devrons utiliser JUnit 4.7 ou version ultérieure avec Surefire 2.16 ou version ultérieure:

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

En un mot, Surefire propose deux façons d'exécuter des tests en parallèle:

  • Multithreading dans un seul processus JVM
  • Forker plusieurs processus JVM

3. Exécution de tests parallèles

Pour exécuter un test en parallèle, nous devons utiliser un testeur qui étend org.junit.runners.ParentRunner .

Cependant, même les tests qui ne déclarent pas d'exécuteur de test explicite fonctionnent, car l'exécuteur par défaut étend cette classe.

Ensuite, pour démontrer l'exécution de tests parallèles, nous utiliserons une suite de tests avec deux classes de test ayant chacune quelques méthodes. En fait, toute implémentation standard d'une suite de tests JUnit ferait l'affaire.

3.1. Utilisation du paramètre parallèle

Tout d'abord, activons le comportement parallèle dans Surefire à l'aide du paramètre parallel . Il indique le niveau de granularité auquel nous souhaitons appliquer le parallélisme.

Les valeurs possibles sont:

  • méthodes - exécute les méthodes de test dans des threads séparés
  • classes - exécute des classes de test dans des threads séparés
  • classesAndMethods - exécute des classes et des méthodes dans des threads séparés
  • suites - exécute les suites en parallèle
  • suitesAndClasses - exécute des suites et des classes dans des threads séparés
  • suitesAndMethods - crée des threads séparés pour les classes et pour les méthodes
  • all - exécute des suites, des classes ainsi que des méthodes dans des threads séparés

Dans notre exemple, nous utilisons tous :

 all 

Deuxièmement, définissons le nombre total de threads que nous voulons que Surefire crée. Nous pouvons le faire de deux manières:

En utilisant threadCount qui définit le nombre maximum de threads que Surefire créera:

10

Ou en utilisant le paramètre useUnlimitedThreads où un thread est créé par cœur de processeur:

true

Par défaut, threadCount est par cœur de processeur. Nous pouvons utiliser le paramètre perCoreThreadCount pour activer ou désactiver ce comportement:

true

3.2. Utilisation des limitations de nombre de threads

Maintenant, disons que nous voulons définir le nombre de threads à créer au niveau de la méthode, de la classe et de la suite. Nous pouvons le faire avec les paramètres threadCountMethods , threadCountClasses et threadCountSuites .

Combinons ces paramètres avec threadCount de la configuration précédente:

2 2 6

Comme nous les avons tous utilisés en parallèle, nous avons défini le nombre de threads pour les méthodes, les suites et les classes. Cependant, il n'est pas obligatoire de définir le paramètre feuille. Surefire en déduit le nombre de threads à utiliser en cas d'omission de paramètres de feuille.

Par exemple, si threadCountMethods est omis, nous devons simplement nous assurer que threadCount > threadCountClasses + threadCountSuites.

Parfois, nous pouvons vouloir limiter le nombre de threads créés pour les classes, les suites ou les méthodes même si nous utilisons un nombre illimité de threads.

Nous pouvons également appliquer des limitations de nombre de threads dans de tels cas:

true 2

3.3. Définition des délais

Parfois, nous pouvons avoir besoin de nous assurer que l'exécution du test est limitée dans le temps.

Pour ce faire, nous pouvons utiliser le paramètre parallelTestTimeoutForcedInSeconds . Cela interrompra les threads en cours d'exécution et n'exécutera aucun des threads en file d'attente une fois le délai écoulé:

5

Une autre option consiste à utiliser parallelTestTimeoutInSeconds .

Dans ce cas, seuls les threads en file d'attente seront arrêtés de s'exécuter:

3.5

Néanmoins, avec les deux options, les tests se termineront par un message d'erreur une fois le délai écoulé.

3.4. Mises en garde

Surefire appelle des méthodes statiques annotées avec @Parameters , @BeforeClass et @AfterClass dans le thread parent. Assurez-vous donc de vérifier les éventuelles incohérences de mémoire ou les conditions de concurrence avant d'exécuter des tests en parallèle.

De plus, les tests qui modifient l'état partagé ne sont certainement pas de bons candidats pour une exécution en parallèle.

4. Exécution des tests dans les projets Maven multi-modules

Jusqu'à présent, nous nous sommes concentrés sur l'exécution de tests en parallèle dans un module Maven.

Mais disons que nous avons plusieurs modules dans un projet Maven. Puisque ces modules sont construits séquentiellement, les tests pour chaque module sont également exécutés séquentiellement.

Nous pouvons changer ce comportement par défaut en utilisant le paramètre -T de Maven qui construit des modules en parallèle . Ceci peut être fait de deux façons.

Nous pouvons soit spécifier le nombre exact de threads à utiliser lors de la construction du projet:

mvn -T 4 surefire:test

Ou utilisez la version portable et spécifiez le nombre de threads à créer par cœur de processeur:

mvn -T 1C surefire:test

Dans tous les cas, nous pouvons accélérer les tests ainsi que les temps d'exécution des builds.

5. Forking JVM

Avec l'exécution de test parallèle via l' option parallel , la concurrence se produit dans le processus JVM à l'aide de threads .

Puisque les threads partagent le même espace mémoire, cela peut être efficace en termes de mémoire et de vitesse. Cependant, nous pouvons rencontrer des conditions de concurrence inattendues ou d'autres échecs de test subtils liés à la concurrence. En fin de compte, partager le même espace mémoire peut être à la fois une bénédiction et une malédiction.

Pour éviter les problèmes de concurrence au niveau des threads, Surefire fournit un autre mode d'exécution de test parallèle: le forking et la concurrence au niveau du processus . L'idée de processus fourchus est en fait assez simple. Au lieu de générer plusieurs threads et de distribuer les méthodes de test entre eux, surefire crée de nouveaux processus et effectue la même distribution.

Puisqu'il n'y a pas de mémoire partagée entre les différents processus, nous ne souffrirons pas de ces bogues subtils de concurrence. Bien sûr, cela se fait au détriment d'une plus grande utilisation de la mémoire et d'un peu moins de vitesse.

Quoi qu'il en soit, pour activer le forking, il suffit d'utiliser la propriété forkCount et de la définir sur une valeur positive:

3

Ici, surefire créera au plus trois fourches à partir de la JVM et y exécutera les tests. La valeur par défaut de forkCount est un, ce qui signifie que maven-surefire-plugin crée un nouveau processus JVM pour exécuter tous les tests dans un module Maven.

La propriété forkCount prend en charge la même syntaxe que -T . Autrement dit, si nous ajoutons le C à la valeur, cette valeur sera multipliée par le nombre de cœurs de processeur disponibles dans notre système. Par exemple:

2.5C

Ensuite, dans une machine à deux cœurs, Surefire peut créer au plus cinq fourches pour l'exécution de tests parallèles.

Par défaut, Surefire réutilisera les fourches créées pour d'autres tests . Cependant, si nous définissons la propriété reuseForks sur false , cela détruira chaque fork après avoir exécuté une classe de test.

Aussi, afin de désactiver la fourche, nous pouvons mettre le forkCount à zéro.

6. Conclusion

Pour résumer, nous avons commencé par activer le comportement multi-thread et définir le degré de parallélisme à l'aide du paramètre parallel . Par la suite, nous avons appliqué des limitations sur le nombre de threads que Surefire devrait créer. Plus tard, nous définissons des paramètres de délai d'expiration pour contrôler les temps d'exécution des tests.

Enfin, nous avons examiné comment réduire les temps d'exécution des builds et donc les temps d'exécution des tests dans les projets Maven multi-modules.

Comme toujours, le code présenté ici est disponible sur GitHub.