Test avec Hamcrest

Test 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é dans JUnit et, en termes simples, il utilise des prédicats existants - appelés classes matrices - pour faire des assertions.

Dans ce tutoriel, nous allonsexplore the Hamcrest API et apprendre à en profiter pour écrire des tests unitaires plus nets et plus intuitifs pour notre logiciel.

2. Configuration de Hamcrest

Nous pouvons utiliserHamcrest avec maven en ajoutant la dépendance suivante à notre fichierpom.xml:


    org.hamcrest
    hamcrest-all
    1.3

La dernière version de cette bibliothèque peut toujours être trouvéehere.

3. Un exemple de test

Hamcrest est couramment utilisé avecjunit et d'autres frameworks de test pour faire des assertions. Plus précisément, au lieu d'utiliser les nombreuses méthodesassert dejunit, nous n'utilisons que l'instructionassertThat unique de l'API avec les correspondances appropriées.

Prenons un exemple qui teste l’égalité de deuxStrings quelle que soit la casse. Cela devrait nous donner une idée claire de la façon dontHamcrest s'intègre dans 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 examinerons plusieurs autres offres communes de matchersHamcrest.

4. Le MatcherObject

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

Pour affirmer que la méthodetoString d'unObject renvoie unString spécifié:

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

Nous pouvons aussi 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 Matcher Bean

Nous pouvons utiliser le Bean matcher deHamcrest pour inspecter les propriétés d'un bean Java.

Supposons le beanPerson 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é,name comme ceci:

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

Nous pouvons également vérifier siPerson a la propriétéaddress, initialisée à New York:

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

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

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

6. Le MatcherCollection

Hamcrest fournit des correspondants pour inspecterCollections.

Vérification simple pour savoir si unCollection est vide:

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

Pour vérifier la taille d'unCollection:

@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 la taille requise:

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

Pour vérifier si unCollection contient des membres donnés, quel que soit l'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 membresCollection sont dans un 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 leur 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 notreCollection est unMap,, nous pouvons utiliser les matchers suivants dans ces fonctions respectives:

Pour vérifier si elle contient une clé donnée:

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

et une valeur donnée:

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

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

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

7. Le MatcherNumber

Les matchersNumber sont utilisés pour effectuer des assertions sur des variables de la classeNumber.

Pour vérifier la condition degreaterThan:

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

Pour vérifier la conditiongreaterThan ouequalTo:

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

Pour vérifier la condition delessThan:

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

Pour vérifier la conditionlessThan ouequalTo:

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

Pour vérifier la condition decloseTo:

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

Faisons très attention au dernier matcher,closeTo. Le premier argument, l'opérande, est celui auquel la cible est comparée et le second argument est l'écart admissible par rapport à l'opérande. Cela signifie que si la cible est l'opérande + la déviation ou la déviation de l'opérande, alors le test passera.

8. Le correcteur de texte

L'assertion surStrings est rendue plus simple, plus claire et plus intuitive avec les correspondances de texte deHamcrest. Nous allons les examiner dans cette section.

Pour vérifier si unString est vide:

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

Pour vérifier si unString est vide ounull:

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

Pour vérifier l'égalité de deuxStrings tout en ignorant les espaces blancs:

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

On peut également vérifier la présence d'une ou plusieurs sous-chaînes dans unString donné dans un ordre donné:

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

Enfin, nous pouvons vérifier l'égalité de deuxStrings quel que soit le cas:

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

9. L'API de base

L'API principale deHamcrest doit être utilisée par des fournisseurs de framework tiers. Cependant, il nous offre d'excellents concepts pour rendre nos tests unitaires plus lisibles, ainsi que des adaptateurs de base qui peuvent être utilisés tout aussi facilement.

Lisibilité avec la constructionis sur un matcher:

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

La constructionis sur un type de données simple:

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

Négation avec la constructionnot sur un matcher:

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

La constructionnot sur un type de données simple:

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

Vérifiez si unString contient une sous-chaîne donnée:

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

Vérifiez si unString commence par une sous-chaîne donnée:

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

Vérifiez si unString se termine par une sous-chaîne donnée:

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

Vérifiez si deuxObjects sont de la même instance:

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

Vérifiez si unObject est une instance d'une classe donnée:

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

Vérifiez si tous les membres d'unCollection remplissent une condition:

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

Vérifiez qu'unString n'est pasnull:

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

Chaîne des conditions ensemble, le test réussit lorsque la cible remplit l'une des conditions, similaire au OU logique:

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

Chaîne des conditions ensemble, le test réussit uniquement lorsque la cible remplit toutes les conditions, similaire à AND logique:

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

10. Un match personnalisé

Nous pouvons définir notre propre matcher en étendantTypeSafeMatcher. Dans cette section, nous allons créer un matcher personnalisé qui permet à un test de réussir uniquement lorsque la cible est un entier positif.

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;
    }

}

Il suffit d'implémenter la méthodematchSafely qui vérifie que la cible est bien un entier positif et la méthodedescribeTo qui produit un message d'échec au cas où le test échouerait.

Voici un test qui utilise notre nouveau matcher personnalisé:

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

et voici un message d'échec que nous recevons depuis que nous avons passé un entier non positif:

java.lang.AssertionError: Expected: a positive integer but: was <-1>

11. Conclusion

Dans ce didacticiel, nous avonsexplored the Hamcrest API et avons appris comment écrire des tests unitaires meilleurs et plus maintenables avec lui.

L'implémentation complète de tous ces exemples et extraits de codecan be found in my Hamcrest github project.