Test avec Google Truth

Test avec Google Truth

1. Vue d'ensemble

Truth est unfluent and flexible open-source testing framework designed to make test assertions and failure messages more readable.

Dans cet article, nous allons explorer les principales fonctionnalités du frameworkTruth et mettre en œuvre des exemples pour présenter ses fonctionnalités.

2. Dépendances Maven

Tout d'abord, nous devons ajouter lestruth ettruth-java8-extension à nospom.xml:


    com.google.truth
    truth
    0.32


    com.google.truth.extensions
    truth-java8-extension
    0.32
    test

Vous pouvez trouver les dernières versions detruth ettruth-java8-extension sur Maven Central.

3. introduction

Truth nous permet d'écrire des assertions lisibles et des messages d'échec pour une variété de classes:

  • Standard Java - primitives, tableaux, chaînes, objets, collections, objets jetables, classes, etc.

  • InstancesJava 8 -Optional etStream

  • ObjetsGuava -Optional,Multimap,Multiset etTable

  • Custom types - en étendant la classeSubject, comme nous le verrons plus tard

Grâce aux classesTruth etTruth8, la bibliothèque fournit des méthodes utilitaires pour écrire des assertions qui fonctionnent sur unsubject, c'est-à-dire la valeur ou l'objet sous test.

Une fois le sujet connu,Truth can reason at compile time about what propositions are known for that subject. Cela lui permet de renvoyer des enveloppes autour de notre valeur qui déclarent des méthodes de proposition spécifiques à ce sujet.

Par exemple, lors d'une affirmation sur une liste,Truth renvoie une instance deIterableSubject définissant des méthodes telles quecontains() etcontainsAnyOf(), entre autres. Lors de l'affirmation sur unMap, il renvoie unMapSubject qui déclare des méthodes commecontainsEntry() etcontainsKey().

4. Commencer

Pour commencer à écrire des assertions, commençons par importer les points d’entrée deTruth:

import static com.google.common.truth.Truth.*;
import static com.google.common.truth.Truth8.*;

Maintenant, écrivons une classe simple que nous utiliserons dans quelques-uns des exemples qui suivent:

public class User {
    private String name = "John Doe";
    private List emails
      = Arrays.asList("[email protected]", "[email protected]");

    public boolean equals(Object obj) {
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        User other = (User) obj;
        return Objects.equals(this.name, other.name);
    }

    // standard constructors, getters and setters
}

Notez la méthode personnaliséeequals(), dans laquelle nous déclarons que deux objetsUser sont égaux si leurs noms le sont.

5. Assertions Java standard

Dans cette section, nous allons voir des exemples détaillés sur la façon d'écrire des assertions de test pour les types Java standard.

5.1. AssertionsObject

Truth fournit le wrapperSubject pour effectuer des assertions sur des objets. Subject est également le parent de tous les autres wrappers de la bibliothèque et déclare des méthodes pour déterminer si unObject, dans notre cas unUser, est égal à un autre objet:

@Test
public void whenComparingUsers_thenEqual() {
    User aUser = new User("John Doe");
    User anotherUser = new User("John Doe");

    assertThat(aUser).isEqualTo(anotherUser);
}

ou s'il est égal à un objet donné dans une liste:

@Test
public void whenComparingUser_thenInList() {
    User aUser = new User();

    assertThat(aUser).isIn(Arrays.asList(1, 3, aUser, null));
}

ou si ce n'est pas:

@Test
public void whenComparingUser_thenNotInList() {
    // ...

    assertThat(aUser).isNotIn(Arrays.asList(1, 3, "Three"));
}

s'il est nul ou non:

@Test
public void whenComparingUser_thenIsNull() {
    User aUser = null;

    assertThat(aUser).isNull();
}

@Test
public void whenComparingUser_thenNotNull() {
    User aUser = new User();

    assertThat(aUser).isNotNull();
}

ou s'il s'agit d'une instance d'une classe particulière:

@Test
public void whenComparingUser_thenInstanceOf() {
    // ...

    assertThat(aUser).isInstanceOf(User.class);
}

Il existe d'autres méthodes d'assertion dans la classeSubject. Pour les découvrir tous, reportez-vous auxSubject documentation.

Dans les sections suivantes,we are going to focus on the most relevant methods for each particular typeTruth prend en charge. Cependant, gardez à l'esprit que toutes les méthodes de la classeSubject peuvent également être appliquées.

5.2. AssertionsInteger,Float, etDouble

Les instancesInteger,Float, etDouble peuvent être comparées pour l'égalité:

@Test
public void whenComparingInteger_thenEqual() {
    int anInt = 10;

    assertThat(anInt).isEqualTo(10);
}

si elles sont plus grandes:

@Test
public void whenComparingFloat_thenIsBigger() {
    float aFloat = 10.0f;

    assertThat(aFloat).isGreaterThan(1.0f);
}

ou plus petit:

@Test
public void whenComparingDouble_thenIsSmaller() {
    double aDouble = 10.0f;

    assertThat(aDouble).isLessThan(20.0);
}

De plus, les instances, Float etDouble peuvent également être vérifiées pour voir si elles sont dans une précision attendue ou non:

@Test
public void whenComparingDouble_thenWithinPrecision() {
    double aDouble = 22.18;

    assertThat(aDouble).isWithin(2).of(23d);
}

@Test
public void whenComparingFloat_thenNotWithinPrecision() {
    float aFloat = 23.04f;

    assertThat(aFloat).isNotWithin(1.3f).of(100f);
}

5.3. AssertionsBigDecimal

Outre les assertions courantes, ce type peut être comparé en ignorant son échelle:

@Test
public void whenComparingBigDecimal_thenEqualIgnoringScale() {
    BigDecimal aBigDecimal = BigDecimal.valueOf(1000, 3);

    assertThat(aBigDecimal).isEqualToIgnoringScale(new BigDecimal(1.0));
}

5.4. AssertionsBoolean

Seules deux méthodes pertinentes sont fournies,isTrue() etisFalse():

@Test
public void whenCheckingBoolean_thenTrue() {
    boolean aBoolean = true;

    assertThat(aBoolean).isTrue();
}

5.5. AssertionsString

Nous pouvons tester si unString commence par un texte particulier:

@Test
public void whenCheckingString_thenStartsWith() {
    String aString = "This is a string";

    assertThat(aString).startsWith("This");
}

De plus, nous pouvons vérifier si la chaîne contient une chaîne donnée, si elle se termine par une valeur attendue ou si elle est vide. Des scénarios de test pour ces méthodes et d'autres sont disponibles dans le code source.

5.6. Assertions de tableau

Nous pouvons vérifierArrays pour voir s'ils sont égaux aux autres tableaux:

@Test
public void whenComparingArrays_thenEqual() {
    String[] firstArrayOfStrings = { "one", "two", "three" };
    String[] secondArrayOfStrings = { "one", "two", "three" };

    assertThat(firstArrayOfStrings).isEqualTo(secondArrayOfStrings);
}

ou s'ils sont vides:

@Test
public void whenCheckingArray_thenEmpty() {
    Object[] anArray = {};

    assertThat(anArray).isEmpty();
}

5.7. AssertionsComparable

En plus de tester si unComparable est supérieur ou inférieur à une autre instance, nous pouvons vérifier s'ils sont au moins une valeur donnée:

@Test
public void whenCheckingComparable_thenAtLeast() {
    Comparable aComparable = 5;

    assertThat(aComparable).isAtLeast(1);
}

En outre, nous pouvons tester si elles sont dans une plage particulière:

@Test
public void whenCheckingComparable_thenInRange() {
    // ...

    assertThat(aComparable).isIn(Range.closed(1, 10));
}

ou dans une liste particulière:

@Test
public void whenCheckingComparable_thenInList() {
    // ...

    assertThat(aComparable).isIn(Arrays.asList(4, 5, 6));
}

Nous pouvons également tester si deux instancesComparable sont équivalentes selon la méthodecompareTo() de la classe.

Tout d'abord, modifions notre classeUser pour implémenter l'interfaceComparable:

public class User implements Comparable {
    // ...

    public int compareTo(User o) {
        return this.getName().compareToIgnoreCase(o.getName());
    }
}

Maintenant, affirmons que deux utilisateurs portant le même nom sont équivalents:

@Test
public void whenComparingUsers_thenEquivalent() {
    User aUser = new User();
    aUser.setName("John Doe");

    User anotherUser = new User();
    anotherUser.setName("john doe");

    assertThat(aUser).isEquivalentAccordingToCompareTo(anotherUser);
}

5.8. AssertionsIterable

Outre l'affirmation de la taille d'une instanceIterable, qu'elle soit vide ou sans doublon, les affirmations les plus courantes sur unIterable sont qu'il contient un élément:

@Test
public void whenCheckingIterable_thenContains() {
    List aList = Arrays.asList(4, 5, 6);

    assertThat(aList).contains(5);
}

qu'il contient n'importe quel élément d'un autreIterable:

@Test
public void whenCheckingIterable_thenContainsAnyInList() {
    List aList = Arrays.asList(1, 2, 3);

    assertThat(aList).containsAnyIn(Arrays.asList(1, 5, 10));
}

et que le sujet possède les mêmes éléments, dans le même ordre, comme un autre:

@Test
public void whenCheckingIterable_thenContainsExactElements() {
    List aList = Arrays.asList("10", "20", "30");
    List anotherList = Arrays.asList("10", "20", "30");

    assertThat(aList)
      .containsExactlyElementsIn(anotherList)
      .inOrder();
}

et s'il est commandé à l'aide d'un comparateur personnalisé:

@Test
public void givenComparator_whenCheckingIterable_thenOrdered() {
    Comparator aComparator
      = (a, b) -> new Float(a).compareTo(new Float(b));

    List aList = Arrays.asList("1", "012", "0020", "100");

    assertThat(aList).isOrdered(aComparator);
}

5.9. AssertionsMap

En plus d'affirmer qu'une instanceMap est vide ou non, ou a une taille spécifique; nous pouvons vérifier s'il a une entrée spécifique:

@Test
public void whenCheckingMap_thenContainsEntry() {
    Map aMap = new HashMap<>();
    aMap.put("one", 1L);

    assertThat(aMap).containsEntry("one", 1L);
}

s'il a une clé spécifique:

@Test
public void whenCheckingMap_thenContainsKey() {
    // ...

    assertThat(map).containsKey("one");
}

ou s'il a les mêmes entrées qu'un autreMap:

@Test
public void whenCheckingMap_thenContainsEntries() {
    Map aMap = new HashMap<>();
    aMap.put("first", 1L);
    aMap.put("second", 2.0);
    aMap.put("third", 3f);

    Map anotherMap = new HashMap<>(aMap);

    assertThat(aMap).containsExactlyEntriesIn(anotherMap);
}

5.10. AssertionsException

Seules deux méthodes d'importance sont fournies pour les objetsException.

Nous pouvons écrire des assertions adressées à la cause de l'exception:

@Test
public void whenCheckingException_thenInstanceOf() {
    Exception anException
      = new IllegalArgumentException(new NumberFormatException());

    assertThat(anException)
      .hasCauseThat()
      .isInstanceOf(NumberFormatException.class);
}

ou à son message:

@Test
public void whenCheckingException_thenCauseMessageIsKnown() {
    Exception anException
      = new IllegalArgumentException("Bad value");

    assertThat(anException)
      .hasMessageThat()
      .startsWith("Bad");
}

5.11. AssertionsClass

Il n’existe qu’une seule méthode importante pour les assertionsClass avec laquelle nous pouvons tester si une classe est attribuable à une autre:

@Test
public void whenCheckingClass_thenIsAssignable() {
    Class aClass = Double.class;

    assertThat(aClass).isAssignableTo(Number.class);
}

6. Assertions Java 8

Optional etStream sont les deux seuls types Java 8 pris en charge parTruth.

6.1. AssertionsOptional

Il existe trois méthodes importantes pour vérifier unOptional.

Nous pouvons tester s'il a une valeur particulière:

@Test
public void whenCheckingJavaOptional_thenHasValue() {
    Optional anOptional = Optional.of(1);

    assertThat(anOptional).hasValue(1);
}

si la valeur est présente:

@Test
public void whenCheckingJavaOptional_thenPresent() {
    Optional anOptional = Optional.of("example");

    assertThat(anOptional).isPresent();
}

ou si la valeur n'est pas présente:

@Test
public void whenCheckingJavaOptional_thenEmpty() {
    Optional anOptional = Optional.empty();

    assertThat(anOptional).isEmpty();
}

6.2. AssertionsStream

Les assertions pour unStream sont très similaires à celles pour unIterable.

Par exemple, nous pouvons tester si unStream particulier contient tous les objets d'unIterable dans le même ordre:

@Test
public void whenCheckingStream_thenContainsInOrder() {
    Stream anStream = Stream.of(1, 2, 3);

    assertThat(anStream)
      .containsAllOf(1, 2, 3)
      .inOrder();
}

Pour plus d'exemples, reportez-vous à la section AssertionsIterable.

7. Assertions de goyave

Dans cette section, nous allons voir des exemples d'assertions pour les types Guava pris en charge dansTruth.

7.1. AssertionsOptional

Il existe également trois méthodes d'assertion importantes pour un GuavaOptional. Les méthodeshasValue() etisPresent() se comportent exactement comme avec un Java 8Optional.

Mais au lieu deisEmpty() pour affirmer qu'unOptional n'est pas présent, nous utilisonsisAbsent():

@Test
public void whenCheckingGuavaOptional_thenIsAbsent() {
    Optional anOptional = Optional.absent();

    assertThat(anOptional).isAbsent();
}

7.2. AssertionsMultimap

Les assertionsMultimap etMap standard sont très similaires.

Une différence notable est que nous pouvons obtenir les valeurs multiples d'une clé dans unMultimap et faire des affirmations sur ces valeurs.

Voici un exemple qui teste si les valeurs de la clé "un" ont une taille de deux:

@Test
public void whenCheckingGuavaMultimap_thenExpectedSize() {
    Multimap aMultimap = ArrayListMultimap.create();
    aMultimap.put("one", 1L);
    aMultimap.put("one", 2.0);

    assertThat(aMultimap)
      .valuesForKey("one")
      .hasSize(2);
}

Pour plus d'exemples, reportez-vous à la section AssertionsMap.

7.3. AssertionsMultiset

Les assertions pour les objetsMultiset incluent celles pour unIterable et une méthode supplémentaire pour vérifier si une clé a un nombre particulier d'occurrences:

@Test
public void whenCheckingGuavaMultiset_thenExpectedCount() {
    TreeMultiset aMultiset = TreeMultiset.create();
    aMultiset.add("example", 10);

    assertThat(aMultiset).hasCount("example", 10);
}

7.4. AssertionsTable

En plus de vérifier sa taille ou son emplacement vide, nous pouvons vérifier unTable pour vérifier s'il contient un mappage particulier pour une ligne et une colonne données:

@Test
public void whenCheckingGuavaTable_thenContains() {
    Table aTable = TreeBasedTable.create();
    aTable.put("firstRow", "firstColumn", "example");

    assertThat(aTable).contains("firstRow", "firstColumn");
}

ou s'il contient une cellule particulière:

@Test
public void whenCheckingGuavaTable_thenContainsCell() {
    Table aTable = getDummyGuavaTable();

    assertThat(aTable).containsCell("firstRow", "firstColumn", "example");
}

De plus, nous pouvons vérifier s’il contient une ligne, une colonne ou une valeur donnée. Voir le code source pour les cas de test pertinents.

8. Messages et étiquettes d'échec personnalisés

Lorsqu'une assertion échoue,Truth affiche des messages très lisibles indiquant exactement ce qui n'a pas fonctionné. Cependant, il est parfois nécessaire d'ajouter plus d'informations à ces messages pour fournir plus de détails sur ce qui s'est passé.

Truth nous permet de personnaliser ces messages d'échec:

@Test
public void whenFailingAssertion_thenCustomMessage() {
    assertWithMessage("TEST-985: Secret user subject was NOT null!")
      .that(new User())
      .isNull();
}

Après avoir exécuté le test, nous obtenons le résultat suivant:

TEST-985: Secret user subject was NOT null!:
  Not true that <[email protected]> is null

En outre, nous pouvons ajouter une étiquette personnalisée qui s'affiche avant notre sujet dans les messages d'erreur. Cela peut être utile lorsqu'un objet n'a pas de représentation de chaîne utile:

@Test
public void whenFailingAssertion_thenMessagePrefix() {
    User aUser = new User();

    assertThat(aUser)
      .named("User [%s]", aUser.getName())
      .isNull();
}

Si nous exécutons le test, nous pouvons voir la sortie suivante:

Not true that User [John Doe]
  (<[email protected]>) is null

9. Les extensions

L'extension deTruth signifie que nous pouvons ajouter la prise en charge des types personnalisés. Pour ce faire, nous devons créer une classe qui:

  • étend la classeSubject ou l'une de ses sous-classes

  • définit un constructeur qui accepte deux arguments - unFailureStrategy et une instance de notre type personnalisé

  • déclare un champ de typeSubjectFactory, queTruth utilisera pour créer des instances de notre sujet personnalisé

  • implémente une méthode statiqueassertThat() qui accepte notre type personnalisé

  • expose notre API d'assertion de test

Maintenant que nous savons comment étendreTruth, créons une classe qui ajoute la prise en charge des objets de typeUser:

public class UserSubject
  extends ComparableSubject {

    private UserSubject(
      FailureStrategy failureStrategy, User target) {
        super(failureStrategy, target);
    }

    private static final
      SubjectFactory USER_SUBJECT_FACTORY
      = new SubjectFactory() {

        public UserSubject getSubject(
          FailureStrategy failureStrategy, User target) {
            return new UserSubject(failureStrategy, target);
        }
    };

    public static UserSubject assertThat(User user) {
        return Truth.assertAbout(USER_SUBJECT_FACTORY).that(user);
    }

    public void hasName(String name) {
        if (!actual().getName().equals(name)) {
            fail("has name", name);
        }
    }

    public void hasNameIgnoringCase(String name) {
        if (!actual().getName().equalsIgnoreCase(name)) {
            fail("has name ignoring case", name);
        }
    }

    public IterableSubject emails() {
        return Truth.assertThat(actual().getEmails());
    }
}

Maintenant, nous pouvons importer statiquement la méthodeassertThat() de notre sujet personnalisé et écrire quelques tests:

@Test
public void whenCheckingUser_thenHasName() {
    User aUser = new User();

    assertThat(aUser).hasName("John Doe");
}

@Test
public void whenCheckingUser_thenHasNameIgnoringCase() {
    // ...

    assertThat(aUser).hasNameIgnoringCase("john doe");
}

@Test
public void givenUser_whenCheckingEmails_thenExpectedSize() {
    // ...

    assertThat(aUser)
      .emails()
      .hasSize(2);
}

10. Conclusion

Dans ce tutoriel, nous avons exploré les possibilités queTruth nous offre pour écrire des tests et des messages d'échec plus lisibles.

Nous avons présenté les méthodes d'assertion les plus populaires pour les types Java et Guava pris en charge, les messages d'échec personnalisés et lesTruthétendus avec des sujets personnalisés.

Comme toujours, le code source complet de cet article peut être trouvéover on Github.