22.3 Java-Assertions-Bibliotheken und AssertJ
Das Problem mit den einfachen Prüfungen ist, dass es am Ende immer auf eine einfache Wahrheitsabfrage herausläuft. Konstruieren wir ein Beispiel. Nehmen wir an, wie wollen prüfen, ob eine Sammlung zwei gewünschte Elemente enthält:
assertTrue( collection.contains(elem1) && collection.contains(elem2) );
Schlägt der Test fehl, bleiben einige Fragen offen:
War das erste oder das zweite Element nicht in der Sammlung? Welches war also vorhanden und welches fehlte?
Was befindet sich insgesamt in der Sammlung?
Natürlich können diese Fragen durch eine eigene Implementierung beantwortet werden, doch es gibt diverse JUnit-Ergänzungen, die helfen, unter anderem: AssertJ (http://assertj.github.io/doc/), Truth (http://google.github.io/truth/) oder Hamcrest (http://hamcrest.org/JavaHamcrest/). Damit lassen sich Tests deklarativer schreiben, sodass sie sich schon fast wie englische Sätze lesen.
22.3.1 AssertJ
AssertJ ist ein populärer Aufsatz auf JUnit. Wir wollen die API für AssertJ kurz der API von JUnit gegenüberstellen. JUnit nutzt einfache assertXXX(…)-Methoden mit ein, zwei oder drei Parametern, und der Test ist gelaufen. Bei AssertJ bildet assertThat(xxx) den Keim für eine zu testende Eigenschaft xxx. Das kann ein primitiver Typ, eine Sammlung, eine Datei, eine Datenbankverbindung oder alles Mögliche sein – und dann folgt mit einer Fluent-API eine Zusicherung von Eigenschaften.
assertXXX(…) von JUnit | assertThat(…) von AssertJ |
---|---|
Object o = new Object(); | Object o = new Object(); |
assertEquals("", Strings.reverse("")); | assertThat(Strings.reverse("")).isEqualTo(""); |
Zunächst fällt auf, dass das erste Argument bei assertThat(…) den Wert beschreibt, den wir haben, der aber bei den sonstigen assertXXX(…)-Methoden immer erst hinter dem erwarteten Wert folgt.
AssertJ einbinden
JUnit enthält AssertJ nicht, sodass wir als Erstes ein Java-Archiv in den Klassenpfad einbinden müssen. Maven-Nutzer schreiben:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.16.1</version>
<scope>test</scope>
</dependency>
Programmbeispiel
Kommen wir auf unser Eingangsbeispiel zurück: Wir möchten testen, ob gewisse Elemente in einer Sammlung sind und andere nicht. Beginnen wir mit dem Setup:
List<String> letters = new ArrayList<>();
Collections.addAll( letters, "a", "b", "c", "d", "e" );
letters.removeAll( Arrays.asList( "b", "d" ) );
Die Liste enthält zunächst die Buchstaben »a« bis »e«, anschließend verlassen »b« und »d« die Liste. Mit AssertJ können wir unterschiedliche Dinge prüfen:
assertThat( letters ).hasSize( 3 );
assertThat( letters ).contains( "a" ).contains( "c" );
assertThat( letters ).contains( "a", "c", "e" ).doesNotContain( "b", "d" );
Klar ist die Fluent-API zu erkennen, wobei sich die Methoden immer danach ergeben, was bei assertThat(xxx) für ein Typ xxx eingesetzt wurde – unterschiedliche Parametertypen führen zu verschiedenen Rückgabetypen. Bei unserer Liste liefert assertThat(letters) den Typ org.assertj.core.api.ListAssert. Hätten wir den Parametertyp int, wäre der Rückgabetyp org.assertj.core.api.AbstractIntegerAssert; bei einen String wäre er org.assertj.core.api.AbstractStringAssert.
Ändern wir die Sammlung ein wenig, und erzeugen wir einen Fehler, indem wir »b« und »e« statt »b« und »d« entfernen. Die Ausgabe ist aussagekräftig:
java.lang.AssertionError:
Expecting:
<["a", "c", "d"]>
to contain:
<["a", "c", "e"]>
but could not find:
<["e"]>