Testen mit Google Truth

Testen mit Google Truth

1. Überblick

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

In diesem Artikel werden wir die Hauptfunktionen desTruth-Frameworks untersuchen und Beispiele implementieren, um seine Funktionen zu demonstrieren.

2. Maven-Abhängigkeiten

Zuerst müssen wir dietruth undtruth-java8-extension zu unserenpom.xml: addieren


    com.google.truth
    truth
    0.32


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

Sie finden die neuesten Versionen vontruth undtruth-java8-extension in Maven Central.

3. Einführung

Truth ermöglicht es uns, lesbare Zusicherungen und Fehlermeldungen für eine Vielzahl von Klassen zu schreiben:

  • Standard Java - Grundelemente, Arrays, Zeichenfolgen, Objekte, Sammlungen, Throwables, Klassen usw.

  • Java 8 -Optional undStream Instanzen

  • Guava -Optional,Multimap,Multiset undTable Objekte

  • Custom types - durch Erweitern der KlasseSubject, wie wir später sehen werden

Über die KlassenTruth undTruth8 bietet die Bibliothek Dienstprogrammmethoden zum Schreiben von Zusicherungen, die fürsubject funktionieren. Dies ist der Wert oder das zu testende Objekt.

Sobald das Thema bekannt ist,Truth can reason at compile time about what propositions are known for that subject. Dies ermöglicht es ihm, Wrapper um unseren Wert zurückzugeben, die für dieses spezielle Thema spezifische Angebotsmethoden deklarieren.

Wenn Sie beispielsweise eine Liste aktivieren, gibtTruth eineIterableSubject-Instanz zurück, die unter anderem Methoden wiecontains() undcontainsAnyOf() definiert. Wenn einMap aktiviert wird, wird einMapSubject zurückgegeben, das Methoden wiecontainsEntry() undcontainsKey() deklariert.

4. Anfangen

Um mit dem Schreiben von Zusicherungen zu beginnen, importieren wir zunächst die Einstiegspunkte vonTruth:

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

Schreiben wir nun eine einfache Klasse, die wir in einigen der folgenden Beispiele verwenden werden:

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
}

Beachten Sie die benutzerdefinierteequals()-Methode, bei der angegeben wird, dass zweiUser-Objekte gleich sind, wenn ihre Namen lauten.

5. Standard Java Assertions

In diesem Abschnitt finden Sie detaillierte Beispiele zum Schreiben von Testzusicherungen für Standard-Java-Typen.

5.1. Object Behauptungen

Truth stellt denSubject-Wrapper zum Ausführen von Zusicherungen für Objekte bereit. Subject ist auch das übergeordnete Element aller anderen Wrapper in der Bibliothek und deklariert Methoden zum Bestimmen, ob einObject, in unserem Fall einUser, einem anderen Objekt entspricht:

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

    assertThat(aUser).isEqualTo(anotherUser);
}

oder wenn es einem bestimmten Objekt in einer Liste entspricht:

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

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

oder wenn nicht:

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

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

ob es null ist oder nicht:

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

    assertThat(aUser).isNull();
}

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

    assertThat(aUser).isNotNull();
}

oder wenn es sich um eine Instanz einer bestimmten Klasse handelt:

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

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

Es gibt andere Assertionsmethoden in der KlasseSubject. Um sie alle zu entdecken, beziehen Sie sich aufSubject documentation.

In den folgenden Abschnitten unterstütztwe are going to focus on the most relevant methods for each particular typeTruth. Beachten Sie jedoch, dass auch alle Methoden in der KlasseSubjectangewendet werden können.

5.2. Integer,Float, undDouble Behauptungen

Integer,Float, undDouble Instanzen können auf Gleichheit verglichen werden:

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

    assertThat(anInt).isEqualTo(10);
}

wenn sie größer sind:

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

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

oder kleiner:

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

    assertThat(aDouble).isLessThan(20.0);
}

Darüber hinaus können auch die Instanzen, Float undDouble überprüft werden, um festzustellen, ob sie innerhalb der erwarteten Genauigkeit liegen oder nicht:

@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. BigDecimal Behauptungen

Abgesehen von den üblichen Behauptungen kann dieser Typ ohne Berücksichtigung seines Umfangs verglichen werden:

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

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

5.4. Boolean Behauptungen

Es werden nur zwei relevante Methoden bereitgestellt,isTrue() undisFalse():

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

    assertThat(aBoolean).isTrue();
}

5.5. String Behauptungen

Wir können testen, ob einString mit einem bestimmten Text beginnt:

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

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

Außerdem können wir überprüfen, ob die Zeichenfolge eine bestimmte Zeichenfolge enthält, ob sie mit einem erwarteten Wert endet oder ob sie leer ist. Testfälle für diese und andere Methoden sind im Quellcode verfügbar.

5.6. Array-Zusicherungen

Wir könnenArrays überprüfen, um festzustellen, ob sie anderen Arrays entsprechen:

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

    assertThat(firstArrayOfStrings).isEqualTo(secondArrayOfStrings);
}

oder wenn sie leer sind:

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

    assertThat(anArray).isEmpty();
}

5.7. Comparable Behauptungen

Neben dem Testen, ob einComparable größer oder kleiner als eine andere Instanz ist, können wir prüfen, ob es sich mindestens um einen bestimmten Wert handelt:

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

    assertThat(aComparable).isAtLeast(1);
}

Wir können auch testen, ob sie in einem bestimmten Bereich liegen:

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

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

oder in einer bestimmten Liste:

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

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

Wir können auch testen, ob zweiComparable-Instanzen gemäß dercompareTo()-Methode der Klasse äquivalent sind.

Ändern wir zunächst die KlasseUser, um die SchnittstelleComparablezu implementieren:

public class User implements Comparable {
    // ...

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

Nehmen wir nun an, dass zwei Benutzer mit demselben Namen gleichwertig sind:

@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. Iterable Behauptungen

Zusätzlich zur Angabe der Größe einerIterable-Instanz, unabhängig davon, ob sie leer ist oder keine Duplikate enthält, bestehen die meisten typischen Aussagen zuIterable darin, dass sie ein Element enthält:

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

    assertThat(aList).contains(5);
}

dass es ein Element eines anderenIterable enthält:

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

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

und dass das Subjekt dieselben Elemente in derselben Reihenfolge wie ein anderes hat:

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

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

und wenn es mit einem benutzerdefinierten Komparator bestellt wurde:

@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. Map Behauptungen

Zusätzlich zur Behauptung, dass die Instanz vonMapleer ist oder nicht oder eine bestimmte Größe hat; wir können überprüfen, ob es einen bestimmten Eintrag hat:

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

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

wenn es einen bestimmten Schlüssel hat:

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

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

oder wenn es die gleichen Einträge wie ein anderesMaphat s:

@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. Exception Behauptungen

FürException Objekte werden nur zwei wichtige Methoden bereitgestellt.

Wir können Aussagen schreiben, die an die Ursache der Ausnahme gerichtet sind:

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

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

oder zu seiner Nachricht:

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

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

5.11. Class Behauptungen

Es gibt nur eine wichtige Methode für die Aussagen vonClass, mit der wir testen können, ob eine Klasse einer anderen zuweisbar ist:

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

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

6. Java 8-Zusicherungen

Optional undStream sind die einzigen zwei Java 8-Typen, dieTruth unterstützt.

6.1. Optional Behauptungen

Es gibt drei wichtige Methoden, umOptional zu überprüfen.

Wir können testen, ob es einen bestimmten Wert hat:

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

    assertThat(anOptional).hasValue(1);
}

Wenn der Wert vorhanden ist:

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

    assertThat(anOptional).isPresent();
}

oder wenn der Wert nicht vorhanden ist:

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

    assertThat(anOptional).isEmpty();
}

6.2. Stream Behauptungen

Aussagen für aStream sind denen fürIterable sehr ähnlich.

Zum Beispiel können wir testen, ob ein bestimmtesStream alle Objekte einesIterable in derselben Reihenfolge enthält:

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

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

Weitere Beispiele finden Sie im Abschnitt Assertions vonIterable.

7. Guaven Behauptungen

In diesem Abschnitt sehen wir Beispiele für Zusicherungen für die unterstützten Guaventypen inTruth.

7.1. Optional Behauptungen

Es gibt auch drei wichtige Assertionsmethoden für eine GuaveOptional. Die MethodenhasValue() undisPresent()verhalten sich genau wie bei Java 8Optional.

Anstelle vonisEmpty(), um zu behaupten, dass keinOptional vorhanden ist, verwenden wirisAbsent():

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

    assertThat(anOptional).isAbsent();
}

7.2. Multimap Behauptungen

Die Aussagen vonMultimap und StandardMap sind sehr ähnlich.

Ein bemerkenswerter Unterschied besteht darin, dass wir die mehreren Werte eines Schlüssels innerhalb vonMultimap erhalten und Aussagen zu diesen Werten treffen können.

Hier ist ein Beispiel, das testet, ob die Werte des Schlüssels "Eins" eine Größe von zwei haben:

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

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

Weitere Beispiele finden Sie im Abschnitt Assertions vonMap.

7.3. Multiset Behauptungen

Zu den Zusicherungen fürMultiset-Objekte gehören die fürIterable und eine zusätzliche Methode, um zu überprüfen, ob ein Schlüssel eine bestimmte Anzahl von Vorkommen aufweist:

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

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

7.4. Table Behauptungen

Neben der Überprüfung der Größe oder der Leerstelle können wir einTableüberprüfen, um zu überprüfen, ob es eine bestimmte Zuordnung für eine bestimmte Zeile und Spalte enthält:

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

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

oder wenn es eine bestimmte Zelle enthält:

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

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

Außerdem können wir prüfen, ob eine bestimmte Zeile, Spalte oder ein bestimmter Wert enthalten ist. Siehe den Quellcode für die relevanten Testfälle.

8. Benutzerdefinierte Fehlermeldungen und Beschriftungen

Wenn eine Zusicherung fehlschlägt, zeigtTruth gut lesbare Meldungen an, die genau angeben, was schief gelaufen ist. Manchmal ist es jedoch erforderlich, diesen Nachrichten weitere Informationen hinzuzufügen, um weitere Details zu dem, was passiert ist, bereitzustellen.

MitTruth können wir diese Fehlermeldungen anpassen:

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

Nach dem Ausführen des Tests erhalten wir die folgende Ausgabe:

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

Außerdem können wir ein benutzerdefiniertes Etikett hinzufügen, das in Fehlermeldungen vor dem Betreff angezeigt wird. Dies kann nützlich sein, wenn ein Objekt keine hilfreiche Zeichenfolgendarstellung hat:

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

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

Wenn wir den Test ausführen, können wir die folgende Ausgabe sehen:

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

9. Erweiterungen

Wenn SieTruth erweitern, können Sie Unterstützung für benutzerdefinierte Typen hinzufügen. Dazu müssen wir eine Klasse erstellen, die:

  • erweitert dieSubject-Klasse oder eine ihrer Unterklassen

  • definiert einen Konstruktor, der zwei Argumente akzeptiert - einFailureStrategy und eine Instanz unseres benutzerdefinierten Typs

  • deklariert ein Feld vom TypSubjectFactory, mit demTruth Instanzen unseres benutzerdefinierten Betreffs erstellt

  • implementiert eine statischeassertThat()-Methode, die unseren benutzerdefinierten Typ akzeptiert

  • macht unsere Test Assertion API verfügbar

Nachdem wir nun wissen, wieTruth erweitert werden, erstellen wir eine Klasse, die Unterstützung für Objekte vom TypUser hinzufügt:

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

Jetzt können wir dieassertThat()-Methode unseres benutzerdefinierten Betreffs statisch importieren und einige Tests schreiben:

@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. Fazit

In diesem Tutorial haben wir die Möglichkeiten untersucht, dieTruth bietet, um besser lesbare Tests und Fehlermeldungen zu schreiben.

Wir haben die beliebtesten Assertionsmethoden für unterstützte Java- und Guava-Typen, angepasste Fehlermeldungen und erweiterteTruth mit benutzerdefinierten Betreffs vorgestellt.

Wie immer finden Sie den vollständigen Quellcode für diesen Artikel inover on Github.