Testen mit Google Truth

1. Übersicht

Truth ist ein fließendes und flexibles Open-Source-Testframework, mit dem Testzusicherungen und Fehlermeldungen besser lesbar gemacht werden können.

In diesem Artikel werden wir die wichtigsten Funktionen des Truth- Frameworks untersuchen und Beispiele implementieren, um seine Funktionen zu demonstrieren.

2. Maven-Abhängigkeiten

Zuerst müssen wir die Wahrheit und die Wahrheit-Java8-Erweiterung zu unserer pom.xml hinzufügen :

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

Sie finden die neuesten Versionen von Truth und Truth-Java8-Erweiterung auf Maven Central.

3. Einführung

Die Wahrheit ermöglicht es uns, lesbare Behauptungen und Fehlermeldungen für eine Vielzahl von Klassen zu schreiben:

  • Standard Java - Grundelemente, Arrays, Zeichenfolgen, Objekte, Sammlungen, Throwables, Klassen usw.
  • Java 8 - Optionale und Stream- Instanzen
  • Guave - Optionale Objekte , Multimap- , Multiset- und Tabellenobjekte
  • Benutzerdefinierte Typen - durch Erweitern der Subject- Klasse, wie wir später sehen werden

Über die Klassen Truth und Truth8 bietet die Bibliothek Dienstprogrammmethoden zum Schreiben von Zusicherungen, die für ein Subjekt gelten , dh für den Wert oder das zu testende Objekt.

Sobald das Thema bekannt ist, kann die Wahrheit zur Kompilierungszeit darüber nachdenken, welche Sätze für dieses Thema bekannt sind . Dies ermöglicht es, Wrapper um unseren Wert zurückzugeben, die für dieses bestimmte Thema spezifische Satzmethoden deklarieren.

Wenn beispielsweise auf einer Liste behauptet, Truth gibt eine IterableSubject Instanz definiert Methoden wie () enthält und containsAnyOf () , unter anderem. Wenn auf einer Geltendmachung Karte , gibt es eine MapSubject , die Methoden wie erklärt containsEntry () und containsKey () .

4. Erste Schritte

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

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 benutzerdefinierte equals () -Methode, bei der angegeben wird, dass zwei Benutzerobjekte 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. Objekt Assertions

Truth bietet den Subject- Wrapper zum Ausführen von Zusicherungen für Objekte. Der Betreff ist auch das übergeordnete Element aller anderen Wrapper in der Bibliothek und deklariert Methoden zum Bestimmen, ob ein Objekt , in unserem Fall ein Benutzer , 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 Subject- Klasse. Informationen zum Entdecken aller Informationen finden Sie in der Betreff- Dokumentation.

In den folgenden Abschnitten konzentrieren wir uns auf die relevantesten Methoden für jeden bestimmten Typ, den Truth unterstützt. Beachten Sie jedoch, dass alle Methoden in der Subject- Klasse auch angewendet werden können.

5.2. Integer- , Float- und Double- Assertions

Integer- , Float- und Double- 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 Float- und Double- Instanzen ü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 Assertions

Neben den üblichen Behauptungen kann dieser Typ ohne Berücksichtigung seiner Größenordnung verglichen werden:

@Test public void whenComparingBigDecimal_thenEqualIgnoringScale() { BigDecimal aBigDecimal = BigDecimal.valueOf(1000, 3); assertThat(aBigDecimal).isEqualToIgnoringScale(new BigDecimal(1.0)); }

5.4. Boolesche Behauptungen

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

@Test public void whenCheckingBoolean_thenTrue() { boolean aBoolean = true; assertThat(aBoolean).isTrue(); }

5.5. String Assertions

Wir können testen, ob ein String 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 prü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

We can check Arrays to see if they are equal to other arrays:

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

or if they are empty:

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

5.7. Comparable Assertions

Besides testing whether a Comparable is greater than or less than another instance, we can check to see if they are at least a given value:

@Test public void whenCheckingComparable_thenAtLeast() { Comparable aComparable = 5; assertThat(aComparable).isAtLeast(1); }

Also, we can test whether they are within a particular range:

@Test public void whenCheckingComparable_thenInRange() { // ... assertThat(aComparable).isIn(Range.closed(1, 10)); }

or in a particular list:

@Test public void whenCheckingComparable_thenInList() { // ... assertThat(aComparable).isIn(Arrays.asList(4, 5, 6)); }

We can also test if two Comparable instances are equivalent according to the class's compareTo() method.

First, let's modify our User class to implement the Comparable interface:

public class User implements Comparable { // ... public int compareTo(User o) { return this.getName().compareToIgnoreCase(o.getName()); } }

Now, let's assert that two users with the same name are equivalent:

@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 Assertions

In addition to asserting the size of an Iterable instance, whether it's empty or has no duplicates, most typical assertions on an Iterable are that it contains some element:

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

that it contains any element of another Iterable:

@Test public void whenCheckingIterable_thenContainsAnyInList() { List aList = Arrays.asList(1, 2, 3); assertThat(aList).containsAnyIn(Arrays.asList(1, 5, 10)); }

and that the subject has the same elements, in the same order, like another:

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

and if it's ordered using a custom comparator:

@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 Assertions

In addition to asserting that a Map instance is empty or not, or has a specific size; we can check if it has a specific entry:

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

if it has a specific key:

@Test public void whenCheckingMap_thenContainsKey() { // ... assertThat(map).containsKey("one"); }

or if it has the same entries as another Map:

@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 Assertions

Only two methods of importance are provided for Exception objects.

We can write assertions addressed to the cause of the exception:

@Test public void whenCheckingException_thenInstanceOf() { Exception anException = new IllegalArgumentException(new NumberFormatException()); assertThat(anException) .hasCauseThat() .isInstanceOf(NumberFormatException.class); }

or to its message:

@Test public void whenCheckingException_thenCauseMessageIsKnown() { Exception anException = new IllegalArgumentException("Bad value"); assertThat(anException) .hasMessageThat() .startsWith("Bad"); }

5.11. Class Assertions

There's only one important method for Class assertions with which we can test whether a class is assignable to another:

@Test public void whenCheckingClass_thenIsAssignable() { Class aClass = Double.class; assertThat(aClass).isAssignableTo(Number.class); }

6. Java 8 Assertions

Optional and Stream are the only two Java 8 types that Truth supports.

6.1. Optional Assertions

There are three important methods to verify an Optional.

We can test whether it has a particular value:

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

if the value is present:

@Test public void whenCheckingJavaOptional_thenPresent() { Optional anOptional = Optional.of("Baeldung"); assertThat(anOptional).isPresent(); }

or if the value is not present:

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

6.2. Stream Assertions

Assertions for a Stream are very similar to the ones for an Iterable.

For example, we can test if a particular Stream contains all objects of an Iterable in the same order:

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

For more examples, please refer to the Iterable Assertions section.

7. Guava Assertions

In this section, we'll see examples of assertions for the supported Guava types in Truth.

7.1. Optional Assertions

There are also three important assertion methods for a Guava Optional. The hasValue() and isPresent() methods behave exactly as with a Java 8 Optional.

But instead of isEmpty() to assert that an Optional is not present, we use isAbsent():

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

7.2. Multimap Assertions

Multimap and standard Map assertions are very similar.

One notable difference is that we can get the multiple values of a key within a Multimap and make assertions on those values.

Here's an example that tests if the values of the “one” key have a size of two:

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

For more examples, please refer to the Map Assertions section.

7.3. Multiset Assertions

Assertions for Multiset objects include the ones for an Iterable and one extra method to verify if a key has a particular number of occurrences:

@Test public void whenCheckingGuavaMultiset_thenExpectedCount() { TreeMultiset aMultiset = TreeMultiset.create(); aMultiset.add("baeldung", 10); assertThat(aMultiset).hasCount("baeldung", 10); }

7.4. Table Assertions

Besides checking its size or where it's empty, we can check a Table to verify if it contains a particular mapping for a given row and column:

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

or if it contains a particular cell:

@Test public void whenCheckingGuavaTable_thenContainsCell() { Table aTable = getDummyGuavaTable(); assertThat(aTable).containsCell("firstRow", "firstColumn", "baeldung"); }

Furthermore, we can check if it contains a given row, column, or value. See the source code for the relevant test cases.

8. Custom Failure Messages and Labels

When an assertion fails, Truth displays very readable messages denoting exactly what went wrong. However, sometimes is necessary to add more information to those messages to provide more details about what happened.

Truth allows us to customize those failure messages:

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

After running the test, we get the following output:

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

Also, we can add a custom label that gets displayed before our subject in error messages. This may come in handy when an object does not have a helpful string representation:

@Test public void whenFailingAssertion_thenMessagePrefix() { User aUser = new User(); assertThat(aUser) .named("User [%s]", aUser.getName()) .isNull(); }

If we run the test, we can see the following output:

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

9. Extensions

Extending Truth means we can add support for custom types. To do this, we need to create a class that:

  • extends the Subject class or one of its subclasses
  • defines a constructor that accepts two arguments – a FailureStrategy and an instance of our custom type
  • declares a field of SubjectFactory type, which Truth will use to create instances of our custom subject
  • implements a static assertThat() method that accepts our custom type
  • exposes our test assertion API

Now that we know how to extend Truth, let's create a class that adds support for objects of type User:

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

Now, we can statically import the assertThat() method of our custom subject and write some 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

In diesem Tutorial haben wir die Möglichkeiten untersucht, die uns die Wahrheit 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 erweiterte Wahrheit mit benutzerdefinierten Betreffs vorgestellt.

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