Overblog
Editer l'article Suivre ce blog Administration + Créer mon blog
Laurent COCAULT

JUnit, on passe la cinquième

A l'occasion d'un "code kata" organisé par un collègue Capgemini, la question s'est posée de la version de JUnit à utiliser pour mener les tests. Dans le cas précis de ce kata, la question ne devait pas susciter une réponse contraignante dans la mesure où chaque développeur travaillait à la fois sur ses tests et son code, contrairement à ce que nous avions pu faire les séances précédentes où le binôme échangeait tests unitaires et code. Néanmoins la question m'a amené à me demander dans quelle mesure il était préférable d'utiliser JUnit5 plutôt que JUnit4. En faisant quelques recherches, je suis tombé sur une conférence Devoxx France de l'année dernière, intitulée "JUnit : il serait temps de passer la 5ème !", dans laquelle Juliette de Rancourt et Julien Topçu échangent sur les nouveautés de JUnit5 et sur les modalités de migration depuis JUnit4. La vidéo m'ayant particulièrement intéressé, je décidais de la proposer pour un déjeuner/débat avec mes collègues. Mais pour préparer le débat qui devait suivre la projection de la conférence, je décidais d'expérimenter moi-même la migration JUnit4 vers JUnit5.

Pour m'essayer à JUnit5, je décidais de reprendre un ancien projet dont les tests étaient codés en JUnit4: RequieM (une librairie de gestion des exigences et de traçabilité dans les tests unitaires). Non seulement cette librairie est de taille raisonnable, de qui me garantissait de ne pas me lancer dans un chantier pharaonique, mais sa thématique même de couverture de tests promettait de me laisser tirer un maximum de bénéfices de JUnit5.

Pour commencer, il est nécessaire de changer la dépendance Maven. Et il n'est pas seulement question de modifier la version de JUnit dans le pom.xml; les identifiants du groupe et de l'artefact changent également.

https://bitbucket.org/lcocault/requiem/commits/5d8d6c29b4a69d0a3611cbc62b4b58b842c3be6f

https://bitbucket.org/lcocault/requiem/commits/5d8d6c29b4a69d0a3611cbc62b4b58b842c3be6f

Une fois la dépendance ajustée, il devient nécessaire de procéder à un nettoyage des dépendances. Non seulement il convient de mettre à jour l'import de la classe "Test" qui a changé de paquetage, mais il faut également procéder à quelques substitutions telles que @BeforeAll au lieu de @BeforeClass ou encore @BeforeEach à la place de @Before.

Par ailleurs, il est nécessaire de remplacer les références à Assert par Assertions. Ce travail peut demander un gros travail de reprise sur le code; dans mon cas, le changement a été plutôt simple dans la mesure où j'ai utilisé sur ce projet des imports statiques de la classe Assert qu'il m'a suffit de remplacer par des imports statiques de la classe Assertions. Ainsi, mes assertions telles que "assertEquals" ou "assertTrue" ont pu rester inchangées. Quelques exemples de substitutions sont présentées ci-dessous:

https://bitbucket.org/lcocault/requiem/commits/5d8d6c29b4a69d0a3611cbc62b4b58b842c3be6f

https://bitbucket.org/lcocault/requiem/commits/5d8d6c29b4a69d0a3611cbc62b4b58b842c3be6f

Au niveau des fonctionnalités apportées par JUnit5, le mieux est de visionner la vidéo précitée, mais je souhaite tout de même en souligner quelques-unes, à commencer par les tests paramétrés. L'application systématique du TDD à laquelle je m'astreins depuis plusieurs mois sur mes projets personnels m'amène à utiliser plus souvent les tests paramétrés. Et JUnit5 apporte une véritable simplification sur la façon de mettre en oeuvre ces tests. Comme le disait un collègue, on est dans le sucre syntaxique, mais c'est le genre de sucre auquel je ne dis pas non tant il facilite l'écriture des tests.

Dans le cas qui nous intéresse, c'est sur une autre nouveauté JUnit5 que je me penché en détails. En effet, le framework de test a abandonné les Runner et Rule au profit de mécanismes d'extensions qui, là encore, ont l'avantage de la simplicité de mise en oeuvre. La création d'une extension m'a semblé être un exemple intéressant d'utilisation avancée de cette librairie. En effet, dans le cas de ma librairie RequieM, il était jusqu'ici nécessaire pour le développeur de gérer le rapport de couverture dans ses tests unitaire en positionnant l'état de couverture des exigences en fonction de la branche du test (nominal ou erronée).

...
report_.add("RESTO-CORE-REST-F-010 v1", CoverageStatus.COVERED);
...
report_.add("RESTO-CORE-REST-F-010 v1", CoverageStatus.NOT_COVERED);

Cette approche était assez peu satisfaisante dans la mesure où il était nécessaire de gérer les cas d'erreur explicitement dans le test plutôt et qu'il était toujours possible d'omettre certaines mises à jour du rapport en fonction de la complexité du test. L'idéal est plutôt de poser une annotation sur chaque test afin que le rapport soit automatiquement alimenté avec l'état d'exécution JUnit.

Pour mettre en place cette solution, on commence par créer l'annotation qui sera posée sur les tests unitaires.

https://bitbucket.org/lcocault/requiem/src/develop/src/main/java/fr/tools/requiem/annotation/Cover.java

https://bitbucket.org/lcocault/requiem/src/develop/src/main/java/fr/tools/requiem/annotation/Cover.java

Il ne s'agit ici que d'une utilisation de mécanismes standards Java. Pour opérer, la magie nécessite la mise en oeuvre d'une extension JUnit5. Ici, on souhaite capturer l'état d'exécution des essais. L'interface à implémenter est donc "AfterTestExecutionCallback" qui demande l'implémentation d'une méthode "public void afterTestExecution(ExtensionContext context) throws Exception". Dans notre cas, c'est une classe CoverageUpdater qui implémentera l'interface et la méthode associée.

JUnit, on passe la cinquième

[...]

https://bitbucket.org/lcocault/requiem/src/develop/src/main/java/fr/tools/requiem/annotation/CoverageUpdater.java

https://bitbucket.org/lcocault/requiem/src/develop/src/main/java/fr/tools/requiem/annotation/CoverageUpdater.java

On pourra noter que le rapport de couverture est géré comme une ressource statique du mécanisme de mise à jour. Ce choix (qui n'est en rien contraint) tient notamment au fait que le rapport est typiquement destiné à être initialisé depuis une méthode @BeforeAll qui doit nécessairement être elle-même statique. Un exemple classique d'utilisation de cette extension est présentée ci-dessous.

https://bitbucket.org/lcocault/requiem/src/develop/src/test/java/fr/tools/requiem/annotation/TestCover.java

https://bitbucket.org/lcocault/requiem/src/develop/src/test/java/fr/tools/requiem/annotation/TestCover.java

Ce bref exemple d'extension illustre la simplicité de mise en oeuvre de JUnit5 et les leviers qu'offe ce framework pour injecter des mécanismes avancés de gestion des tests. A l'issue de ces travaux, il ne fait plus pour moi de doute que pour accélérer vos développements, il faut passer la cinquième.

Partager cet article
Repost0
Pour être informé des derniers articles, inscrivez vous :
Commenter cet article