Utilisez AssertJ pour des tests plus explicites

  EN
 Lecture : 4 min
 chop
 Pas encore commenté
  (MàJ : 2021-12-03)

On ne va pas discuter aujourd’hui le bien-fondé des tests. Je veux simplement vous suggérer une bibliothèque qui permet de les écrire différemment et, selon moi, les rendre plus compréhensibles.

Mon problème avec les tests #

De toute ma carrière, JUnit est le seul framework de tests que j’ai utilisé en Java (enfin, les seuls, puisque j’ai commencé avec JUnit 4 et me suis depuis familiarisé avec Jupiter). Vous pouvez me traiter d’ignorant, mais j’apprécie ce framework. Il couvre la plupart des bases nécessaires à tous mes tests.

Il y a cependant un détail qui revient souvent me tracasser, cependant : l’écriture des assertions. Les lire n’est pas toujours évident, pour plusieurs raisons :

  • Certaines erreurs sont récurrentes, comme l’inversion de la valeur « attendue » et la valeur « réelle », ce qui peut faire de la recherche d’erreur un enfer si on ne prend pas le temps de se demander ce qu’est vraiment censé faire le code.
  • Cela figure probablement parmi le code le plus procédural qu’il nous soit donné d’écrire en Java. Juste une suite d’instructions.
  • Je n’ai jamais connu un seul développeur (moi compris) qui prenne le temps d’écrire des messages pour les assertions qui échoueraient, et même les commentaires sont rares (mais je pense qu’il s’agit là d’un sujet pour un autre billet).

Je pourrais continuer mais vous saisissez l’idée.

Il existe plusieurs façons d’écrire les tests de façon plus lisible. Nous pourrions par exemple évoquer Cucumber et Gherkin, mais une autre fois. Aujourd’hui, je vais me concentrer sur la désignation chaînée et plus particulièrement sur AssertJ.

La désignation chaînée à la rescousse #

Savez-vous ce qu’est la désignation chaînée ? En anglais, on parle de fluent interface. Il s’agit d’une approche de programmation orientée objet qui consiste à chaîner les appels de méthode au sein d’une seule instruction. Le modèle du Builder est un exemple.

1final Book gameOfThrones = Book.builder()
2    .title("Le Trône de fer")
3    .author("George R. R. Martin")
4    .series("Le Trône de fer")
5    .build();

Je préfère cette approche à ce que je connaissais avant ça. J’ai troujours trouvé pénible d’appeler les accesseurs un par un…

1final Book gameOfThrones = new Book();
2gameOfThrones.setTitle("Le Trône de fer");
3gameOfThrones.setAuthor("George R. R. Martin");
4gameOfThrones.setSeries("Le Trône de fer");

… et le constructeur prenant tous les arguments permet lui aussi de créer des objets immutables, mais il devient rapidement verbeux et difficile à manipuler :

  • Vous devez connaître la position de chaque paramètre : les revues de code sont plus complexes sans la documentation ou un IDE efficace.
  • Il faut soit plusieurs variantes du constructeur, soit le risque d’appeler en laissant de nombreux arguments null.
1// Je ne connais pas l'année de publication, je laisse vide
2final Book gameOfThrones = new Book("Le Trône de fer", "George R. R. Martin", null, "Le Trône de fer");

Cet exemple est bien entendu très grossier, mais il vous donne un rapide aperçu de la désignation chaînée. Vous pouvez aussi penser à la Stream API.

Toutefois, il y a un point intéressant commun à plusieurs implémentations que j’ai vues de la désignation chaînée : elle donne l’impression de remplacer les listes d’instructions par des phrases, quelque chose que même un non-codeur pourrait lire. AssertJ figure parmi ces implémentations.

Démarrer avec AssertJ #

À quoi ressembleraient vos tests écrits avec AssertJ ? Voyons quelques exemples :

 1assertThat(tyrion)
 2    .isNotEqualTo(robert)
 3    .isIn(lannisterFamily);
 4
 5assertThat(melisandre.getName())
 6    .startsWith("Mel")
 7    .endsWith("dre")
 8    .isEqualToIgnoringCase("melisandre");
 9
10assertThat(nedsChildren)
11    .hasSize(5)
12    .contains(robb, arya)
13    .doesNotContain(joffrey);

Figurez-vous écrire la même chose avec les assertions de JUnit et dites-moi ce que vous préférez.

Pour commencer à utiliser AssertJ, il vous faut d’abord récupérer la dépendance. En Maven :

1<dependency>
2  <groupId>org.assertj</groupId>
3  <artifactId>assertj-core</artifactId>
4  <version>3.20.2</version>
5  <scope>test</scope>
6</dependency>

Ensuite, dans vos classes de tests, il ne reste plus qu’à remplacer les imports d’assertions JUnit par la ligne suivante :

1import static org.assertj.core.api.Assertions.assertThat;

À partir de là, vous pouvez utiliser l’autocomplétion de votre éditeur, la documentation officielle ou certains tutos de quelque renommée pour voir quelles assertions vous sont offertes.

assertThat est la méthode principale, mais plusieurs autres sont disponibles. La documentation liste les suivantes :

 1import static org.assertj.core.api.Assertions.assertThat;  // main one
 2import static org.assertj.core.api.Assertions.atIndex; // for List assertions
 3import static org.assertj.core.api.Assertions.entry;  // for Map assertions
 4import static org.assertj.core.api.Assertions.tuple; // when extracting several  properties at once
 5import static org.assertj.core.api.Assertions.fail; // use when writing exception tests
 6import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; // idem
 7import static org.assertj.core.api.Assertions.filter; // for Iterable/Array assertions
 8import static org.assertj.core.api.Assertions.offset; // for floating number assertions
 9import static org.assertj.core.api.Assertions.anyOf; // use with Condition
10import static org.assertj.core.api.Assertions.contentOf; // use with File assertions

Sur cette rapide introduction, je vous laisse faire vos propres essais et me dire ce que vous en pensez. En ce qui me concerne, je sais que, depuis que je l’ai découvert, j’ai tendance à remplacer toutes mes assertions JUnit. Vous pouvez d’ailleurs voir des exemples d’AssertJ avec JUnit 5 au sein de mon projet Download proxy.