Tester avec Hamcrest

1. Vue d'ensemble

Hamcrest est le framework bien connu utilisé pour les tests unitaires dans l'écosystème Java. Il est intégré à JUnit et, en termes simples, il utilise des prédicats existants - appelés classes matrices - pour faire des assertions.

Dans ce tutoriel, nous explorerons l'API Hamcrest et apprendrons à en profiter pour écrire des tests unitaires plus nets et plus intuitifs pour notre logiciel.

2. Configuration de Hamcrest

Nous pouvons utiliser Hamcrest avec maven en ajoutant la dépendance suivante à notre fichier pom.xml :

 org.hamcrest hamcrest-all 1.3 

La dernière version de cette bibliothèque est toujours disponible ici.

3. Un exemple de test

Hamcrest est couramment utilisé avec junit et d'autres frameworks de test pour faire des assertions. Plus précisément, au lieu d'utiliser les nombreuses méthodes d' assert de junit , nous n'utilisons que l' instruction assertThat unique de l'API avec les correspondants appropriés.

Regardons un exemple qui teste l' égalité de deux String, quelle que soit la casse. Cela devrait nous donner une idée claire de la façon dont Hamcrest s'intègre à une méthode de test:

public class StringMatcherTest { @Test public void given2Strings_whenEqual_thenCorrect() { String a = "foo"; String b = "FOO"; assertThat(a, equalToIgnoringCase(b)); } }

Dans les sections suivantes , nous allons jeter un oeil à plusieurs autres Matchers communes de Hamcrest offres.

4. Object Matcher

Hamcrest fournit des correspondants pour faire des assertions sur des objets Java arbitraires.

Pour affirmer que la méthode toString d'un Object renvoie une chaîne spécifiée :

@Test public void givenBean_whenToStringReturnsRequiredString_thenCorrect(){ Person person=new Person("Barrack", "Washington"); String str=person.toString(); assertThat(person,hasToString(str)); }

Nous pouvons également vérifier qu'une classe est une sous-classe d'une autre:

@Test public void given2Classes_whenOneInheritsFromOther_thenCorrect(){ assertThat(Cat.class,typeCompatibleWith(Animal.class)); } }

5. Le Bean Matcher

Nous pouvons utiliser Hamcrest Bean » de matcher pour inspecter les propriétés d'un bean Java.

Supposons le bean Person suivant :

public class Person { String name; String address; public Person(String personName, String personAddress) { name = personName; address = personAddress; } }

Nous pouvons vérifier si le bean a la propriété, nom comme suit:

@Test public void givenBean_whenHasValue_thenCorrect() { Person person = new Person("Baeldung", 25); assertThat(person, hasProperty("name")); }

Nous pouvons également vérifier si Person a la propriété d' adresse , initialisée à New York:

@Test public void givenBean_whenHasCorrectValue_thenCorrect() { Person person = new Person("Baeldung", "New York"); assertThat(person, hasProperty("address", equalTo("New York"))); }

Nous pouvons également vérifier si deux objets Person sont construits avec les mêmes valeurs:

@Test public void given2Beans_whenHavingSameValues_thenCorrect() { Person person1 = new Person("Baeldung", "New York"); Person person2 = new Person("Baeldung", "New York"); assertThat(person1, samePropertyValuesAs(person2)); } 

6. La collection Matcher

Hamcrest fournit des apparieurs pour l'inspection des collections .

Vérification simple pour savoir si une collection est vide:

@Test public void givenCollection_whenEmpty_thenCorrect() { List emptyList = new ArrayList(); assertThat(emptyList, empty()); }

Pour vérifier la taille d'une collection:

@Test public void givenAList_whenChecksSize_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, hasSize(4)); }

Nous pouvons également l'utiliser pour affirmer qu'un tableau a une taille requise:

@Test public void givenArray_whenChecksSize_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayWithSize(4)); }

Pour vérifier si une collection contient des membres donnés, quel que soit leur ordre:

@Test public void givenAListAndValues_whenChecksListForGivenValues_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, containsInAnyOrder("beans", "text", "collections", "number")); }

Pour affirmer en outre que les membres de la collection sont dans l'ordre donné:

@Test public void givenAListAndValues_whenChecksListForGivenValuesWithOrder_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, contains("collections", "beans", "text", "number")); }

Pour vérifier si un tableau a un seul élément donné:

@Test public void givenArrayAndValue_whenValueFoundInArray_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, hasItemInArray("text")); }

Nous pouvons également utiliser un matcher alternatif pour le même test:

@Test public void givenValueAndArray_whenValueIsOneOfArrayElements_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat("text", isOneOf(hamcrestMatchers)); }

Ou encore nous pouvons faire la même chose avec un matcher différent comme ceci:

@Test public void givenValueAndArray_whenValueFoundInArray_thenCorrect() { String[] array = new String[] { "collections", "beans", "text", "number" }; assertThat("beans", isIn(array)); }

Nous pouvons également vérifier si le tableau contient des éléments donnés quel que soit l'ordre:

@Test public void givenArrayAndValues_whenValuesFoundInArray_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayContainingInAnyOrder("beans", "collections", "number", "text")); }

Pour vérifier si le tableau contient des éléments donnés mais dans l'ordre donné:

@Test public void givenArrayAndValues_whenValuesFoundInArrayInOrder_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayContaining("collections", "beans", "text", "number")); }

Lorsque notre collection est une carte, nous pouvons utiliser les correspondants suivants dans ces fonctions respectives:

Pour vérifier s'il contient une clé donnée:

@Test public void givenMapAndKey_whenKeyFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasKey("blogname")); }

et une valeur donnée:

@Test public void givenMapAndValue_whenValueFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasValue("baeldung")); }

et enfin une entrée donnée (clé, valeur):

@Test public void givenMapAndEntry_whenEntryFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasEntry("blogname", "baeldung")); }

7. Le nombre Matcher

Les matchers Number sont utilisés pour effectuer des assertions sur des variables de la classe Number .

Pour vérifier une condition supérieure à celle :

@Test public void givenAnInteger_whenGreaterThan0_thenCorrect() { assertThat(1, greaterThan(0)); }

Pour vérifier la condition supérieure ou égale à :

@Test public void givenAnInteger_whenGreaterThanOrEqTo5_thenCorrect() { assertThat(5, greaterThanOrEqualTo(5)); }

Pour vérifier moins que la condition:

@Test public void givenAnInteger_whenLessThan0_thenCorrect() { assertThat(-1, lessThan(0)); }

Pour vérifier la condition lessThan ou equalTo :

@Test public void givenAnInteger_whenLessThanOrEqTo5_thenCorrect() { assertThat(-1, lessThanOrEqualTo(5)); }

Pour vérifier la condition closeTo :

@Test public void givenADouble_whenCloseTo_thenCorrect() { assertThat(1.2, closeTo(1, 0.5)); }

Let's pay close attention to the last matcher, closeTo. The first argument, the operand, is the one to which the target is compared and the second argument is the allowable deviation from the operand . This means that if the target is operand+deviation or operand-deviation, then the test will pass.

8. The Text Matcher

Assertion on Strings is made easier, neater and more intuitive with Hamcrest‘s text matchers. We are going to take a look at them in this section.

To check if a String is empty:

@Test public void givenString_whenEmpty_thenCorrect() { String str = ""; assertThat(str, isEmptyString()); }

To check if a String is empty or null:

@Test public void givenString_whenEmptyOrNull_thenCorrect() { String str = null; assertThat(str, isEmptyOrNullString()); }

To check for equality of two Strings while ignoring white space:

@Test public void given2Strings_whenEqualRegardlessWhiteSpace_thenCorrect() { String str1 = "text"; String str2 = " text "; assertThat(str1, equalToIgnoringWhiteSpace(str2)); }

We can also check for the presence of one or more sub-strings in a given String in a given order:

@Test public void givenString_whenContainsGivenSubstring_thenCorrect() { String str = "calligraphy"; assertThat(str, stringContainsInOrder(Arrays.asList("call", "graph"))); }

Finally, we can check for equality of two Strings regardless of case:

@Test public void given2Strings_whenEqual_thenCorrect() { String a = "foo"; String b = "FOO"; assertThat(a, equalToIgnoringCase(b)); }

9. The Core API

The Hamcrest core API is to be used by third-party framework providers. However, it offers us some great constructs to make our unit tests more readable and also some core matchers that can be used just as easily.

Readability with the is construct on a matcher:

@Test public void given2Strings_whenIsEqualRegardlessWhiteSpace_thenCorrect() { String str1 = "text"; String str2 = " text "; assertThat(str1, is(equalToIgnoringWhiteSpace(str2))); }

The is construct on a simple data type:

@Test public void given2Strings_whenIsEqual_thenCorrect() { String str1 = "text"; String str2 = "text"; assertThat(str1, is(str2)); }

Negation with the not construct on a matcher:

@Test public void given2Strings_whenIsNotEqualRegardlessWhiteSpace_thenCorrect() { String str1 = "text"; String str2 = " texts "; assertThat(str1, not(equalToIgnoringWhiteSpace(str2))); }

The not construct on a simple data type:

@Test public void given2Strings_whenNotEqual_thenCorrect() { String str1 = "text"; String str2 = "texts"; assertThat(str1, not(str2)); }

Check if a String contains a given sub-string:

@Test public void givenAStrings_whenContainsAnotherGivenString_thenCorrect() { String str1 = "calligraphy"; String str2 = "call"; assertThat(str1, containsString(str2)); }

Check if a String starts with given sub-string:

@Test public void givenAString_whenStartsWithAnotherGivenString_thenCorrect() { String str1 = "calligraphy"; String str2 = "call"; assertThat(str1, startsWith(str2)); }

Check if a String ends with given sub-string:

@Test public void givenAString_whenEndsWithAnotherGivenString_thenCorrect() { String str1 = "calligraphy"; String str2 = "phy"; assertThat(str1, endsWith(str2)); }

Check if two Objects are of the same instance:

@Test public void given2Objects_whenSameInstance_thenCorrect() { Cat cat=new Cat(); assertThat(cat, sameInstance(cat)); }

Check if an Object is an instance of a given class:

@Test public void givenAnObject_whenInstanceOfGivenClass_thenCorrect() { Cat cat=new Cat(); assertThat(cat, instanceOf(Cat.class)); }

Check if all members of a Collection meet a condition:

@Test public void givenList_whenEachElementGreaterThan0_thenCorrect() { List list = Arrays.asList(1, 2, 3); int baseCase = 0; assertThat(list, everyItem(greaterThan(baseCase))); }

Check that a String is not null:

@Test public void givenString_whenNotNull_thenCorrect() { String str = "notnull"; assertThat(str, notNullValue()); }

Chain conditions together, test passes when target meets any of the conditions, similar to logical OR:

@Test public void givenString_whenMeetsAnyOfGivenConditions_thenCorrect() { String str = "calligraphy"; String start = "call"; String end = "foo"; assertThat(str, anyOf(startsWith(start), containsString(end))); }

Chain conditions together, test passes only when target meets all conditions, similar to logical AND:

@Test public void givenString_whenMeetsAllOfGivenConditions_thenCorrect() { String str = "calligraphy"; String start = "call"; String end = "phy"; assertThat(str, allOf(startsWith(start), endsWith(end))); }

10. A Custom Matcher

We can define our own matcher by extending TypeSafeMatcher. In this section, we will create a custom matcher which allows a test to pass only when the target is a positive integer.

public class IsPositiveInteger extends TypeSafeMatcher { public void describeTo(Description description) { description.appendText("a positive integer"); } @Factory public static Matcher isAPositiveInteger() { return new IsPositiveInteger(); } @Override protected boolean matchesSafely(Integer integer) { return integer > 0; } }

We need only to implement the matchSafely method which checks that the target is indeed a positive integer and the describeTo method which produces a failure message in case the test does not pass.

Here is a test that uses our new custom matcher:

@Test public void givenInteger_whenAPositiveValue_thenCorrect() { int num = 1; assertThat(num, isAPositiveInteger()); }

and here is a failure message we get since we have passed in a non-positive integer:

java.lang.AssertionError: Expected: a positive integer but: was 

11. Conclusion

In this tutorial, we have explored the Hamcrest API and learnt how we can write better and more maintainable unit tests with it.

L'implémentation complète de tous ces exemples et extraits de code peut être trouvée dans mon projet github Hamcrest.