AssertJ - Einführung: Unterschied zwischen den Versionen
K (Ullrich verschob die Seite AssertJ Einführung nach AssertJ - Einführung, ohne dabei eine Weiterleitung anzulegen) |
|||
(4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
[[Category:Java]] | |||
= Die grundlegende Syntax = | = Die grundlegende Syntax = | ||
Die Struktur von AssertJ-Anweisungen könnte einfacher nicht sein. | Die Struktur von AssertJ-Anweisungen könnte einfacher nicht sein. | ||
Jede Assertion in AssertJ folgt dem gleichen Aufbau; umgangsprachlich läßt sie so ausdrücken: | Jede Assertion in AssertJ folgt dem gleichen Aufbau; umgangsprachlich läßt sie so ausdrücken: | ||
{{quote|assert that | {{quote|assert that X has property Y}} | ||
oder auf Deutsch: | oder auf Deutsch: | ||
{{quote|stelle sicher, daß | {{quote|stelle sicher, daß X die Eigenschaft Y besitzt}} | ||
Sie besteht also aus zwei Teilen: dem zu prüfenden Objekt X und der Prüfung Y. | Sie besteht also aus zwei Teilen: dem zu prüfenden Objekt X und der Prüfung Y. Nahezu jede Assertion beginnt mit dem Aufruf der selben statischen Methode: | ||
{{java|code=Assertions.assertThat(...)}} | {{java|code=Assertions.assertThat(...)}} | ||
Das hat einen unschätzbaren Vorteil gegenüber JUnit-Assertion, denn man braucht sich für den Einstieg nur eine | |||
typischerweise beschreibt er eine lokale Variable, ein Objekt, oder | einzige Methode zu merken. Bei der Auswahl der Test-Methoden hilft die IDE via Code-Completion. | ||
Auf dem | |||
Die assertThat-Methode hat als einzigen Parameter einen Java-Ausdruck der das zu prüfende Objekt liefert. | |||
Der Ausdruck kann so ziemlich alles sein, typischerweise beschreibt er eine lokale Variable, ein Objekt, oder | |||
einen Methoden-Aufruf. Auf dem dambei erzeugten Objekt kann man -- je nach Typ -- eine Reihe von Methoden aufrufen, | |||
die die zu prüfende Eigenschaft darstellen. | |||
Betrachten wir mal als Beispiel die Prüfung, ob {{java|2 + 2}} tatsächlich {{java|4}} ergibt. Dafür schreiben wir zunächst einmal | Betrachten wir mal als Beispiel die Prüfung, ob {{java|2 + 2}} tatsächlich {{java|4}} ergibt. Dafür schreiben wir zunächst einmal | ||
{{java|code= | {{java|code=Assertions.asserThat(2 + 2)}} | ||
Assertions.asserThat(2 + 2) | Tippen wir nun einen Punkt ein, bietet die IDE eine Fülle von Methoden an -- wir möchten auf Gleichheit | ||
}} | |||
Tippen wir nun einen Punkt ein, bietet | |||
testen, wählen für unser Beispiel {{java|.isEqualTo()}} aus und schreiben | testen, wählen für unser Beispiel {{java|.isEqualTo()}} aus und schreiben | ||
{{java|code= | {{java|code=Assertions.asserThat(2 + 2).isEqualTo(4);}} | ||
Schon ist die Assertion fertig. Aus der Fülle der Prüf-Methdoen werden in den folgenden Abschnitten einige | |||
}} | der meistverwendeten vorgestellt. | ||
Schon ist die Assertion fertig. | |||
= Ausführung = | = Ausführung = | ||
Geht die Assertion schief, bemüht sich AssertJ um eine möglichst hilfreiche Meldung: | Geht die Assertion schief, bemüht sich AssertJ um eine möglichst hilfreiche Meldung: | ||
Zeile 31: | Zeile 33: | ||
org.junit.ComparisonFailure: [2 + 2] expected:<[5]> but was:<[4]> | org.junit.ComparisonFailure: [2 + 2] expected:<[5]> but was:<[4]> | ||
= Objekte = | =Objekte= | ||
==Gleichheit== | ==Gleichheit== | ||
Normalerweise vergleicht man Objekte auf inhaltliche Gelichheit über die {{java|.equals()}} Methode. Das geht in AssertJ mit: | Normalerweise vergleicht man Objekte auf inhaltliche Gelichheit über die {{java|.equals()}} Methode. Das geht in AssertJ mit: | ||
{{java|code=assertThat(x). | {{java|code=assertThat(x).isEqualTo(Y)}} | ||
Vergleicht man enum-Ausprägungen oder möchte sicherstellen -- was einem Vergleich mit == entspricht -- verwendet man | Vergleicht man enum-Ausprägungen oder möchte sicherstellen daß zwei Objekte identisch sind | ||
-- was einem Vergleich mit == entspricht -- verwendet man | |||
{{java|code=assertThat(x).isSameAs(Y)}} | {{java|code=assertThat(x).isSameAs(Y)}} | ||
Zeile 46: | Zeile 48: | ||
}} | }} | ||
==Welcher Typ?== | ==Welcher Typ?== | ||
Um zu prüfen ob das Objekt Instanz einer Klasse ist oder ncht prüft man mit | |||
{{java|code=assertThat(X).isInstanceOf(Foo.class);}} | {{java|code= | ||
assertThat(X).isInstanceOf(Foo.class); | |||
assertThat(X).isNotInstanceOf(Foo.class) | |||
}} | |||
Diese Bedinung ist auch dann erfüllt, wenn X einer Unterklasse von {{java|Foo}} angehört (oder nicht). | |||
Möchte man sichergehen, daß exakt gegen {{java|Foo}} geprüft wird, verwendet man stattdessen | |||
{{java|code= | |||
assertThat(X).isExactlyInstanceOf(Foo.class); | |||
assertThat(X).isNotExactlyInstanceOf(Foo.class) | |||
}} | |||
==Boolean und boolean== | ==Boolean und boolean== | ||
Für die Boolean-Typen stehen zwei Methoden mit offensichtlichem Verhalten zur Verfügung: | Für die Boolean-Typen stehen zwei Methoden mit offensichtlichem Verhalten zur Verfügung: | ||
Zeile 73: | Zeile 84: | ||
während {{java|containsInstanceOf()}} den Typ von {{java|x.get()}} prüft. | während {{java|containsInstanceOf()}} den Typ von {{java|x.get()}} prüft. | ||
== | == <span id="matches"></span>Der Eierlegende Wollmilch-Test == | ||
Nahezu jede Test-Situation für die man nicht sofort eine passende Lösungin AssertJ findet, | |||
läßt sich mit einem {{lambda}}-Ausdruck bewältigen der en Test beschreibt. Nicht zuletzt, | |||
weil man hier beliebige Funktionen aufrufen kann die jeden erdenklichen Test ausführen. | |||
Das | Das ist hilfreich, wenn man feststeckt und weitermachen möchte. Man sollte jedoch erwägen | ||
{{java| | -- zu einem späteren Zeitpunkt -- nachzuforschen ob's nicht eleganter geht. | ||
Die magische Methode heißt {{java|matches}} und nimmt ein Prädikat vom passenden Typ als Parameter: | |||
{{java|code= | {{java|code= | ||
assertThat("foo").matches((String s) -> s.trim().length() == 3); | |||
}} | }} | ||
Hier in einem anschaulichen, wenn auch einigermaßen sinnfreien Beispiel. | |||
=Exceptions= | =Exceptions= | ||
Zeile 205: | Zeile 143: | ||
}); | }); | ||
}} | }} | ||
= Mehrere Assertions in einem Test = | |||
Eine Grundregel für Unit-Tests lautet: ''Eine'' Assertion je Test. Warum sollte man also mehrere Assertions in einem | |||
Test ausführen? Möchte man nach Ausführung eins Tests z.B. prüfen ob ein Adreß-Objekt korrekt befüllt ist, ist es in | |||
der Regel einfacher Alle Felder in einem Test abzufragen. Bisweilen hat man auch komplexere Situationen zu prüfen, | |||
etwa wenn ein Objekt-Geflecht erzeugt wird und die Struktur geprüft wird. Dann möchte man nicht einfach die Meldung | |||
bekommen "geht nicht" sondern genauere Information darüber was nicht stimmt. | |||
Das Problem bei der Test-Ausführung besteht nun darin, daß das Test-Framework -- in der Regeln JUnit -- nach dem | |||
Auftreten des ersten Fehlers den Test abbricht. Man führt den Test dann wieder und wieder aus und fixt jedesmal | |||
''einen'' Fehler. | |||
AssertJ bietet -- mit den Soft-Assertions -- die Möglichkeit, die Assertion-Ergebnisse zu sammeln und am Ende der | |||
Test-Ausführung gesammelt und komplett auszuwerten. Es giebt zwei Möglichkeiten die Soft-Assertions anzuwenden. | |||
Hier zunächst die konventionelle Variante: | |||
{{java|code= | |||
SoftAssertions bunch = new SoftAssertions(); | |||
bunch.assertThat(adresse.getStrasse()).isEqulaTo("Alexanderplatz"); | |||
bunch.assertThat(adresse.getHausnummer()).isEqulaTo(15); | |||
bunch.assertThat(adresse.getPostfach()).isNull(); | |||
bunch.assertAll(); | |||
}} | |||
Die Assertions werden in einem {{java|SoftAssertions}-Objekt gesammelt und im Anschluß an die Ausführung | |||
durch den Aufruf von {{java|assertAll()}} gesammelt ausgewertet. Der Knackpunkt bei dieser Variante ist, | |||
daß der Aufruf leicht vergessen werden kann. Dann wird im Unit-Test -- unbemerkt -- nichts geprüft! | |||
Dieses Problem kann man mit der {{lambda}}-Variante umgehen: | |||
{{java|code= | |||
SoftAssertions.assertSoftly(bunch -> { | |||
bunch.assertThat(adresse.getStrasse()).isEqulaTo("Alexanderplatz"); | |||
bunch.assertThat(adresse.getHausnummer()).isEqulaTo(15); | |||
bunch.assertThat(adresse.getPostfach()).isNull(); | |||
)); | |||
}} | |||
Anstelle der unmittelbaren Ausführung wird der Block nun in einen {{lambda}}-Ausdruck verpackt. | |||
Das {{java|SoftAssertions}-Objekt wird von der statischen Methode {{java|assertSoftly}} erzeugt, | |||
mit dem {{lambda}}-Ausdruck befüllt und {{java|assertAll()}} wird am Ende automatisch ausgeführt. | |||
Der Block des {{lambda}}-Ausdrucks kann zudem alle beliebigen Java-Anweisungen enthalten, | |||
wie zusätzliche Berechnungen, lokale Variablen etcetera. |
Aktuelle Version vom 17. Juli 2021, 14:07 Uhr
Die grundlegende Syntax
Die Struktur von AssertJ-Anweisungen könnte einfacher nicht sein. Jede Assertion in AssertJ folgt dem gleichen Aufbau; umgangsprachlich läßt sie so ausdrücken:
assert that X has property Y
oder auf Deutsch:
stelle sicher, daß X die Eigenschaft Y besitzt
Sie besteht also aus zwei Teilen: dem zu prüfenden Objekt X und der Prüfung Y. Nahezu jede Assertion beginnt mit dem Aufruf der selben statischen Methode:
Assertions.assertThat(...)
Das hat einen unschätzbaren Vorteil gegenüber JUnit-Assertion, denn man braucht sich für den Einstieg nur eine einzige Methode zu merken. Bei der Auswahl der Test-Methoden hilft die IDE via Code-Completion.
Die assertThat-Methode hat als einzigen Parameter einen Java-Ausdruck der das zu prüfende Objekt liefert. Der Ausdruck kann so ziemlich alles sein, typischerweise beschreibt er eine lokale Variable, ein Objekt, oder einen Methoden-Aufruf. Auf dem dambei erzeugten Objekt kann man -- je nach Typ -- eine Reihe von Methoden aufrufen, die die zu prüfende Eigenschaft darstellen.
Betrachten wir mal als Beispiel die Prüfung, ob 2 + 2
tatsächlich 4
ergibt. Dafür schreiben wir zunächst einmal
Assertions.asserThat(2 + 2)
Tippen wir nun einen Punkt ein, bietet die IDE eine Fülle von Methoden an -- wir möchten auf Gleichheit
testen, wählen für unser Beispiel .isEqualTo()
aus und schreiben
Assertions.asserThat(2 + 2).isEqualTo(4);
Schon ist die Assertion fertig. Aus der Fülle der Prüf-Methdoen werden in den folgenden Abschnitten einige der meistverwendeten vorgestellt.
Ausführung
Geht die Assertion schief, bemüht sich AssertJ um eine möglichst hilfreiche Meldung:
Assertions.assertThat(2+2).isEqualTo(5); org.junit.ComparisonFailure: expected:<[5]> but was:<[4]>
Sollte die Meldung nicht ausreichen, kann man nach dem Aufruf von assertThat
eine Erklärung einfügen indem man
die .as()
-Methode aufruft:
Assertions.assertThat(2 + 2).as("2 + 2").isEqualTo(5); org.junit.ComparisonFailure: [2 + 2] expected:<[5]> but was:<[4]>
Objekte
Gleichheit
Normalerweise vergleicht man Objekte auf inhaltliche Gelichheit über die .equals()
Methode. Das geht in AssertJ mit:
assertThat(x).isEqualTo(Y)
Vergleicht man enum-Ausprägungen oder möchte sicherstellen daß zwei Objekte identisch sind -- was einem Vergleich mit == entspricht -- verwendet man
assertThat(x).isSameAs(Y)
Null oder nicht Null
Dafür gibt es zwei Methoden mit naheliegendem Namen:
assertThat(x).isNull() assertThat(x).isNotNull()
Welcher Typ?
Um zu prüfen ob das Objekt Instanz einer Klasse ist oder ncht prüft man mit
assertThat(X).isInstanceOf(Foo.class); assertThat(X).isNotInstanceOf(Foo.class)
Diese Bedinung ist auch dann erfüllt, wenn X einer Unterklasse von Foo
angehört (oder nicht).
Möchte man sichergehen, daß exakt gegen Foo
geprüft wird, verwendet man stattdessen
assertThat(X).isExactlyInstanceOf(Foo.class); assertThat(X).isNotExactlyInstanceOf(Foo.class)
Boolean und boolean
Für die Boolean-Typen stehen zwei Methoden mit offensichtlichem Verhalten zur Verfügung:
Assertions.assertThat(x).isTrue() Assertions.assertThat(x).isFalse()
Die Methoden funktionieren für beide Boolean-Typen, für Boolean
ohne die Gefahr einer Null-Pointer-Exception.
Optional
Bevor man den Inhalt eines Optional-Objekts prüfen kann, muß man ihn erst auspacken;
ein Job den AssertJ für uns übernimmt und dabei auch darauf achtet, ob statt eines validen Objekts
vielleicht null
übergeben wurde:
Assertions.assertThat(x).contains("foo")
Ist der Inhalt egal, kann man auch direkt die Präsenz oder Abwesenheit eines Wertes abfragen:
Assertions.assertThat(x).isPresent() Assertions.assertThat(x).isNotPresent()
Statt .isNotPresent()
kann auch .empty()
verwendet werden.
Interessiert nur der Typ des enthaltenen Objekts, hilft AssertJ uns auch:
Assertions.assertThat(x).containsInstanceOf(String.class)
Man beachte den Unterschied zu .instanceOf()
, das den Typ von x
prüft,
während containsInstanceOf()
den Typ von x.get()
prüft.
Der Eierlegende Wollmilch-Test
Nahezu jede Test-Situation für die man nicht sofort eine passende Lösungin AssertJ findet, läßt sich mit einem λ-Ausdruck bewältigen der en Test beschreibt. Nicht zuletzt, weil man hier beliebige Funktionen aufrufen kann die jeden erdenklichen Test ausführen.
Das ist hilfreich, wenn man feststeckt und weitermachen möchte. Man sollte jedoch erwägen
-- zu einem späteren Zeitpunkt -- nachzuforschen ob's nicht eleganter geht.
Die magische Methode heißt matches
und nimmt ein Prädikat vom passenden Typ als Parameter:
assertThat("foo").matches((String s) -> s.trim().length() == 3);
Hier in einem anschaulichen, wenn auch einigermaßen sinnfreien Beispiel.
Exceptions
Mit der assertThat
-Methode kann nicht getestet werden, ob bei einem Methoden-Aufruf eine Exception geworfen wird oder nicht.
Das funktioniert deshalb nicht, weil der Ausdruck, der assetThat
als Argument übergeben wird ausgewertet wird bevor
die assertThat
-Methode ausgeführt wird.
Für den Test auf Exceptions wird die Ausführung in einen λ-Ausdrck verpackt, dieser einer geeigneten Methode übergeben und as Ergebnis bei dessen Ausführung analysiert -- wie funktioniert das konkret?
Bei der ersten Variante wird ein λ-Ausdruck übergeben und anschließend die erwartete Exception angegeben.
Im Beispiel wird die Methode foo
des Objekts subject
aufgerufen:
Assertions.assertThatThrownBy(() -> subject.foo()).isInstanceOf(NullPointerException.class);
Die erwartete Exception-Klasse wird von der Methode isInstanceOf()
geprüft.
Die zweite Variante legt erst die erwartete Exeption fest und prüft dann den λ-Ausdruck:
Assertions.assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> subjekt.foo());
Die zweite Variante verstößt gegen das sonst in AssertJ durchgängige Konzept erst den Test-Aufruf und dann das erwartete Ergebnis anzugeben. Es gibt jedoch einige Methode für oft benötigte Exceptions:
Assertions.assertThatIllegalStateException().isThrownBy(() -> subjekt.foo()); Assertions.assertThatIllegalArgumentException().isThrownBy(() -> subjekt.foo()); Assertions.assertThatNullPointerException().isThrownBy(() -> subjekt.foo()); Assertions.assertThatIOException().isThrownBy(() -> subjekt.foo());
Geht das auch ohne λ?
Grundsätzlich sind λ-Ausdrücke und anonyme Klassen austauschbare Konzepte.
Man kann daher λ-Ausdrücke durch Klassen ersetzen die das geeignete (funktionale) Interface implementieren.
Die Methode assertThatThrownBy()
akzeptiert als Parameter ein Objekt das das Interface
ThrowingCallable
implementiert. Daher kann man die erste Variante mit einer anonymen Klasse so schreiben:
Assertions.assertThatThrownBy(new ThrowingCallable() { @Override public void call() throws Throwable { subjekt.foo(); } }).isInstanceOf(NullPointerException.class);
Die zweite Variante sieht dann -- wir verwenden hier die Spezial-Methode für Nullpointer-Exceptions -- so aus:
Assertions.assertThatNullPointerException().isThrownBy(new ThrowingCallable() { @Override public void call() throws Throwable { subjekt.foo(); } });
Mehrere Assertions in einem Test
Eine Grundregel für Unit-Tests lautet: Eine Assertion je Test. Warum sollte man also mehrere Assertions in einem Test ausführen? Möchte man nach Ausführung eins Tests z.B. prüfen ob ein Adreß-Objekt korrekt befüllt ist, ist es in der Regel einfacher Alle Felder in einem Test abzufragen. Bisweilen hat man auch komplexere Situationen zu prüfen, etwa wenn ein Objekt-Geflecht erzeugt wird und die Struktur geprüft wird. Dann möchte man nicht einfach die Meldung bekommen "geht nicht" sondern genauere Information darüber was nicht stimmt.
Das Problem bei der Test-Ausführung besteht nun darin, daß das Test-Framework -- in der Regeln JUnit -- nach dem Auftreten des ersten Fehlers den Test abbricht. Man führt den Test dann wieder und wieder aus und fixt jedesmal einen Fehler.
AssertJ bietet -- mit den Soft-Assertions -- die Möglichkeit, die Assertion-Ergebnisse zu sammeln und am Ende der Test-Ausführung gesammelt und komplett auszuwerten. Es giebt zwei Möglichkeiten die Soft-Assertions anzuwenden. Hier zunächst die konventionelle Variante:
SoftAssertions bunch = new SoftAssertions(); bunch.assertThat(adresse.getStrasse()).isEqulaTo("Alexanderplatz"); bunch.assertThat(adresse.getHausnummer()).isEqulaTo(15); bunch.assertThat(adresse.getPostfach()).isNull(); bunch.assertAll();
Die Assertions werden in einem {{java|SoftAssertions}-Objekt gesammelt und im Anschluß an die Ausführung
durch den Aufruf von assertAll()
gesammelt ausgewertet. Der Knackpunkt bei dieser Variante ist,
daß der Aufruf leicht vergessen werden kann. Dann wird im Unit-Test -- unbemerkt -- nichts geprüft!
Dieses Problem kann man mit der λ-Variante umgehen:
SoftAssertions.assertSoftly(bunch -> { bunch.assertThat(adresse.getStrasse()).isEqulaTo("Alexanderplatz"); bunch.assertThat(adresse.getHausnummer()).isEqulaTo(15); bunch.assertThat(adresse.getPostfach()).isNull(); ));
Anstelle der unmittelbaren Ausführung wird der Block nun in einen λ-Ausdruck verpackt.
Das {{java|SoftAssertions}-Objekt wird von der statischen Methode assertSoftly
erzeugt,
mit dem λ-Ausdruck befüllt und assertAll()
wird am Ende automatisch ausgeführt.
Der Block des λ-Ausdrucks kann zudem alle beliebigen Java-Anweisungen enthalten, wie zusätzliche Berechnungen, lokale Variablen etcetera.