Lors de l’écriture de tests unitaires, on peut se retrouver à écrire ce genre de code :
assertEquals(42, answer); assertFalse(answer == 34); assertTrue(info.matches("color") || info.matches("colour")); assertTrue(colorsList.contains("red")); assertTrue(s instanceof MagicString);
Mais JUnit propose depuis la version 4.4 une nouvelle syntaxe à base de Matchers. En fait, cette syntaxe n’est pas totalement nouvelle : elle est empruntée à JMock qui possède déjà les Matchers JMock pour décrire les contraintes d’un mock. L’idée vient de Joe Walnes dans son article Flexible JUnit assertions with assertThat().
Les assertions précédentes peuvent alors s’écrire de la manière suivante :
assertThat(answer, is(42)); assertThat(answer, is(not(34))); assertThat(info, either(containsString("color")).or(containsString("colour"))); assertThat(colorsList, hasItem("red")); assertThat(s, instanceOf(MagicString.class));
Les avantages sont multiples :
- Ça se lit mieux : le développeur communique donc mieux son intention aux autres développeurs, ce qui est essentiel !
- Contrairement aux méthodes assertEquals et consorts, on ne risque pas de tromper dans l’ordre des arguments : la syntaxe d’assertEquals est
assertEquals([expected], [actual])mais beaucoup de gens se trompent et écriventassertEquals(answer, 42). Avec assertThat, aucun risque. - Les matchers peuvent être modifiés: par exemple :
not(s)pour inverser la condition,either(s).or(t)pour combiner des conditions,each(s)pour appliquer une condition sur chaque élément d’une collection,afterFiveSeconds(s)pour attendre 5 secondes avant de tester… - En cas d’échec de l’assertion, les messages sont beaucoup plus lisibles. Si on reprend l’exemple précédents, avec les assertXxx classiques, les messages ressembleraient à ça :
assertEquals(42, answer); => expected:<42> but was:<36> assertFalse(answer == 34); => junit.framework.AssertionFailedError: assertTrue(info.matches("color") || info.matches("colour")); => junit.framework.AssertionFailedError: assertTrue(colorsList.contains("red")); => junit.framework.AssertionFailedError: assertTrue(s instanceof MagicString); => junit.framework.AssertionFailedError:Avec assertThat, les message ressemblent plutôt à ça :
assertThat(answer, is(42)); => Expected: is <42> got: <36> assertThat(answer, is(not(34))); => Expected: is not <34> got: <34> assertThat(info, either(containsString("color")).or(containsString("colour"))); => Expected: (a string containing "colour" or a string containing "color") got: "rainbow" assertThat(colorsList, hasItem("red")); => Expected: a collection containing "hello" got: <[bonjour, ia orana]> assertThat(s, instanceOf(MagicString.class)); => Expected: an instance of sandbox.FooTest$MagicString got: "polop"Inutile de dire qu’il est beaucoup plus facile de débugguer des tests écrits avec assertThat…
- Il est plus facile d’écrire des Matcher maison plutôt que des méthodes assertXxxx maison. En plus ces Matchers pourront être réutilisés dans des tests JMock.
- C’est sexy
Pour plus d’informations, je vous recommande la lecture de ce billet. Il est en français, il est très détaillé et je suis tombé dessus en écrivant cet article.
En java, n’importe quel objet implémentant l’interface