AssertJ pour des assertions plus expressives

Écrire des tests, c’est bien, écrire des bons tests c’est encore mieux. La technique given when then est une des bonnes pratiques pour écrire des tests (unitaires ou pas d’ailleurs). Les 3 concepts sont en effet nécessaires, y-compris le dernier qui correspond aux assertions. En effet, un test n’apporte vraiment de la valeur que si il procède à des vérifications sur le résultat de son exécution. Ça parait évident mais je suis tombé à plusieurs reprises sur des tests qui exécutaient du code sans rien vérifier…

En Java, les outils de base les plus populaires pour écrire des tests (JUnit et TestNG) sont assez pauvres pour écrire les assertions. Heureusement, il existe des outils permettant de gagner à la fois en puissance et en expressivité pour exprimer les assertions.

La limite des assertions basiques

J’ai pendant longtemps utilisé les assertions basiques proposées par JUnit, à savoir assertEquals et ses variantes. Dans la majorité des cas j’arrivais facilement à exprimer ce que je souhaitais. Mais quelques fois ça devenait assez laborieux et je n’arrivais parfois pas à vérifier ce que je voulais par manque d’expressivité. C’était notamment le cas pour les listes. Et quand bien même j’y arrivais, j’effectuais ma vérification, je renvoyais la valeur dans un booléen et j’utilisais assertTrue ou assertFalse. Et quand le test échouait, j’avais un message d’erreur du type false is not true. Pas évident pour comprendre d’où vient le problème !

Python propose également la fameuse fonction assertEqual (sans s). Malheureusement, les paramètres expected et actual sont inversés par rapport à celle de JUnit. Quand je me suis mis à coder plus fréquemment en Python je m’emmêlais systématiquement les pinceaux sur l’ordre des paramètres. L’inversion des paramètres ne change pas la vérification en elle-même, en revanche elle présente des informations fausses dans le rapport d’erreur du test qui peuvent facilement induire en erreur lorsqu’on cherche à corriger le problème.

Outils spécifiques

Il existe différents outils permettant de simplifier l’écriture des assertions en Java. J’ai testé dans un premier temps Hamcrest. J’ai ensuite découvert FEST Fluent Assertions dont l’API est plus facile à lire et à utiliser (on bénéficie notamment de l’autocompletion de l’IDE). Mon choix s’est finalement porté sur AssertJ dans la mesure où il s’inspire de tous les bons concepts de FEST et il est activement développé (FEST semble être à l’abandon).

Assert that …

Les assertions AssertJ (comme celles de Hamcrest ou de FEST) commencent toujours par assertThat(actualValue). Cette fonction renvoie une structure de données qui encapsule la valeur sur laquelle on travaille et propose d’effectuer des vérifications adaptées à son type.

Cela permet d’écrire presque comme on le ferait en langage naturel : assert that result is equal to 3.

Cette façon d’écrire les assertions résout par la même occasion le problème de l’inversion des paramètres expected et actual de assertEquals.

Assertions simples

Les assertions les plus courantes consistent à vérifier l’égalité de deux valeurs. Voilà l’alternative au assertEquals que nous propose AssertJ :

assertThat(daltons.averell().getName()).isEqualTo("Averell");

On peut également l’appliquer à des objets (attention à bien définir la méthode equals) :

assertThat(daltons.averell()).isEqualTo(new Person("Averell"));

Assertions sur des types plus complexes

L’expressivité d’AssertJ devient très intéressante lorsqu’on travaille avec des types plus complexes mais néanmoins courants. C’est typiquement le cas avec une collection. Comment vérifier qu’une collection contient tel et tel éléments ? Comment s’assurer qu’ils sont bien dans l’ordre.

Les assertions spécifiques aux collections offrent une expressivité très puissante.

Je veux vérifier que les Daltons sont bien 4 :

assertThat(daltons.all()).hasSize(4);

Jack fait bien partie des 4 Daltons :

assertThat(daltons.all()).contains(daltons.jack());

Tous les Daltons sont là et dans l’ordre :

assertThat(daltons.all()).containsExactly(daltons.joe(), daltons.william(), daltons.jack(), daltons.averell());

Bien entendu, on peut faire toutes ces opérations avec le bon vieux assertEquals, mais le code sera bien moins lisible et surtout le message d’erreur sera un peu plus parlant que false is not true qu’on a quand on vérifie des conditions avec JUnit. Si, par exemple, les Daltons ne sont pas renvoyés dans l’ordre, le message d’erreur est explicite : Actual and expected have the same elements but not in the same order, at index 1 actual element was: <Person(name=Averell)> whereas expected element was: <Person(name=William)>.

De la même façon, AssertJ fournit une expressivité équivalente adapté aux structures de données les plus courantes du langage. Dans sa version 3 il supporte les nouvelles structures de données de Java 8 (les optional par exemple) et permet d’écrire :

assertThat(optionalString).isEmpty();

ou encore :

assertThat(optionalString).contains("Hello world!");

Des extensions permettent de supporter les structures des outils fréquemment utilisés tels que guava et Joda-Time.

Exceptions

AssertJ propose également un moyen de vérifier que des exceptions sont bien jetées par une méthode. Cette expressivité est particulièrement bienvenue parce que JUnit est très pauvre en la matière. Grâce aux lambda expressions de Java 8, on peut vérifier très facilement les exceptions :

assertThatThrownBy(() -> calculator.divide(100, 0))
        .isInstanceOf(ArithmeticException.class)
        .hasMessage("/ by zero");

Conclusion

AssertJ est un outil que je recommande vivement. Il est facile à prendre en main notamment grâce à sa super documentation. Il est puissant et extensible, ce qui lui permet de s’adapter à des situations bien plus évoluées que celles que j’ai décrites dans cet article.

Il cohabite sans problème avec JUnit, il est donc possible de s’y mettre petit à petit. Au revoir assertEquals !

L’image d’en-tête provient de Flickr.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *