In der agilen Softwareentwicklung sind automatisierte Tests nicht mehr wegzudenken. Sie dienen nicht nur der Qualitätssicherung, sondern auch dazu, die geforderte Funktionalität der Software zu spezifizieren. In vielen Projekten muss eine Test-Dokumentation mit einem Release ausgeliefert werden.
Sehr hilfreich sind Testberichte, welche bei der Test-Ausführung automatisch generiert werden, so wie das bei dem Java-Testframework JUnit der Fall ist. Außer den Entwickelnden selbst kann diese aber kaum jemand verstehen. Dagegen lassen sich mit JGiven Tests schreiben, die automatisch einen fachlich verständlichen Testbericht generieren. Die Test-Dokumentation ist dadurch als Bestandteil des Quellcodes stets aktuell.
In diesem Artikel beschäftigen wir uns damit, das Framework näher kennenzulernen. Wir sehen uns die Berichte an, die mit JGiven erstellt werden können, aber auch die Implementierung der Testklassen, welche solche Berichterstellung gewährleisten. Schließlich widmen wir uns der Frage, wann die simpleren JUnit-Berichte völlig ausreichend sind, und wann doch die Unterstützung von JGiven sinnvoll sein kann.
Gegeben: Framework JGiven
Bei JGiven handelt es sich um ein Open-Source BDD-Framework für Java. Die Technik des BDD (Behavior Driven Development) ist unter anderem dafür bekannt, dass die Anforderungen an die zu entwickelnde Software mittels Szenarien formuliert werden, also beispielhaften Situationen, anhand derer beschrieben wird, wie die Software sich zu verhalten hat. Als Format der Beschreibung solcher Szenarien dient häufig die sogenannte Gherkin-Syntax, welche sich durch die Given-When-Then-Struktur (Gegeben-Wenn-Dann) auszeichnet. Der Zweck hierbei ist, dass diese strukturierte Schreibweise während der Entwicklung in automatisierte Tests überführt werden kann, welche das geforderte Verhalten verifizieren.
Das Java-Framework JGiven unterstützt den Entwicklungsprozess in der Weise, dass es z.B. Klassen bietet, welche als eine Szenario-Klasse oder als eine Komponente des Szenarios (Given, When bzw. Then) dienen. Mit anderen Worten, wir implementieren den jeweiligen Teil der Testlogik in den Methoden der entsprechenden Klasse und setzen die Tests in einer Szenario-Klasse aus diesen Methoden zusammen. Ausschlaggebend ist hierbei die Benamung der einzelnen Methoden. Sie sollen nämlich dem umgesetzten Schritt aus dem geforderten Szenario entsprechen.
Wird ein solcher Test ausgeführt, so generiert JGiven einen Bericht, welcher das getestete Szenario wiedergibt. JGiven verarbeitet dabei die Methodennamen automatisch zu Sätzen, sodass der Testbericht einem typisch formulierten Szenario entspricht. Auf diese Weise eignet sich dieser Bericht dazu, mit allen Beteiligten besprochen zu werden. Der Testbericht wird in erster Linie als reiner Text und im JSON-Format erstellt. Anschließend können aus den JSON-Berichten ein interaktiver HTML- und auch ein AsciiDoc-Report erstellt werden und so als Teil der Softwaredokumentation dienen.
Wenn: Wir generieren Reports mit JUnit und JGiven
Reports mit JUnit
Auch ohne den Einsatz von JGiven können wir während der Testausführung automatisiert Reports generieren. Um beispielsweise JUnit 5-Berichte zu erzeugen, kann in einem Maven-Projekt das Surefire-Plugin verwendet werden.
Als Beispiel nehmen wir das Spring Boot-Projekt petclinic. Mittels mvn test
werden alle Tests in dem Projekt ausgeführt und entsprechende Berichte unter 'target/surefire-reports' als TXT- und XML-Dateien abgelegt. Dabei generiert Surefire einen Bericht pro Testklasse.
Für die Klasse ClinicServiceTests.java
sieht der Text-Bericht wie folgt aus:
Wir sehen also, dass zehn Tests ausgeführt wurden, kein Test fehlerhaft war und auch wie lange die Testausführung gedauert hat, jedoch sehen wir nicht, welche Funktionalitäten getestet wurden.
Der zu der Klasse gehörende XML-Bericht ist deutlich ausführlicher, wenn auch nicht weniger technisch: Er zeigt Informationen über die gesetzten Properties (Versionen von Bibliotheken und dem Betriebssystem, Infos zu dem Git-Stand, Pfade und Spracheinstellungen, und diverse andere) und auch einen Bericht für jede einzelne Testmethode, zum Beispiel:
Der Name der Testmethode (shouldFindOwnersByLastName
) lässt vermuten, dass der Test die Funktionalität verifiziert, die Haustier-Besitzer-Daten anhand des Nachnamens liefert. Der Bericht ist jedoch kaum für jemanden nützlich, der*die nicht an der eigentlichen Implementierung beteiligt ist. Für die eventuelle Fehlersuche oder benötigte Code-Anpassungen kann er wiederum wertvoll sein.
Schließlich können wir den Befehl mvn surefire-report:report
verwenden, um einen HTML-Bericht zu erzeugen, welcher alle Testberichte beinhaltet. In diesem sind wiederum die technischen Informationen zu finden (Namen der Klassen, Methoden und Packages, Ausführungszeit, Erfolg bzw. Fehlschlag), wie beispielsweise im folgenden Ausschnitt zu sehen:
Reports mit JGiven
Die bisherigen Tests und Berichte bieten keine Details zu den Abläufen, welche verifiziert und getestet werden. Außerdem sind sie für eine Diskussion mit dem Fachbereich oder für die Nutzung als Softwaredokumentation nicht gut geeignet. Abhilfe schaffen Tests, für welche JGiven genutzt wird. Diese testen das Verhalten der Anwendung und nicht einzelne Methoden. Unabhängig von der konkreten Implementierung, der Klassen- und Methodenstruktur wird das korrekte Verhalten gewährleistet.
In der Testklasse ClinicServiceTests
gibt es eine Testmethode mit dem Namen shouldAddNewVisitForPet
. Diese Methode verifiziert, dass die Anzahl der Besuche sich bei jedem Besuch um eins erhöht. Wollen wir dieses Verhalten mit JGiven testen, dann könnte der Report der Testmethode wie folgt aussehen:
Um einen solchen Bericht zu erhalten, fügen wir zunächst die passende JGiven-Abhängigkeit zu unserem Projekt hinzu. JGiven bietet eigene Implementierungen für verschiedene Testframeworks, für unsere Petclinic-Beispiele verwenden wir die Version für JUnit 5 und nutzen folgende Maven-Abhängigkeit:
Listing 1: Maven-Abhängigkeit für die JGiven-Implementierung für JUnit 5
Ein klassischer JGiven-Test hat die folgende grundlegende Struktur: Die Testklasse erbt von ScenarioTest und nutzt für die eigentlichen Tests jeweils eine Given-, When- und Then-Klasse, in welchen die entsprechenden Stadien der Tests implementiert sind. Die Given-Klasse beinhaltet somit die Methoden, welche einen initialen Zustand für einen Test herstellen. Die Methoden der When-Klasse enthalten die Ausführungen der einzelnen Testfälle. Anschließend verifizieren die Methoden der Then-Klasse die erwarteten Zustände nach den ausgeführten Tests.
Konkret würden wir für das in Abbildung 4 beschriebene Szenario folgende Testklasse erstellen:
Listing 2: Testklasse mit JGiven
Wir erweitern also die JGiven-Klasse ScenarioTest
, wobei wir die Stage
-Klassen GivenPet
, WhenVisit
und ThenAmount
angeben. In diesen Klassen ist die eigentliche Testlogik vorhanden, und zwar in den Methoden, aus denen unser Test besteht. Die Namen der Methoden entsprechen dem Inhalt des Berichts, den wir generieren wollen. Hier empfiehlt sich die Snakecase-Schreibweise, da JGiven für den Bericht einfach die Unterstriche durch Leerzeichen ersetzt. Außerdem können wir $ als Platzhalter nutzen, an diese Stellen werden im Bericht die Werte der Parameter eingesetzt.
Sehen wir uns nun die einzelnen Klassen an, die jeweils von der JGiven-Klasse Stage
erben.
In der GivenPet
-Klasse bereiten wir den initialen Zustand unseres Tests vor: Wir erstellen das Haustier mit dem gegebenen Namen und der gegebenen Anzahl der bisherigen Klinikbesuche:
Listing 3: Given-Klasse
Nun veranlassen wir einen Besuch des Haustiers in der Klasse WhenVisit
:
Listing 4: When-Klasse
Anschließend verifizieren wir in der Klasse ThenAmount, dass die Anzahl der Besuche des Haustiers mit der erwarteten übereinstimmt:
Listing 5: Then-Klasse
All diese Methoden haben die folgende Gemeinsamkeit: Sie geben am Ende mittels self()
die Instanz der eigenen Klasse zurück. Das ist keine Notwendigkeit, wird aber häufig eingesetzt, um die Methoden miteinander verbinden zu können und so bei Bedarf komplexere Abfolgen an Schritten auszuführen.
Um den Bericht zu generieren, führen wir den Test mittels mvn test
aus. Die reine Textversion des Berichts (siehe Abbildung 4) wird dadurch direkt in die Konsole geschrieben. Außerdem erstellt JGiven einen JSON-Bericht, in dem die Details zu der Ausführung enthalten sind, wie z.B. ob der Test erfolgreich war und wie lange die Ausführung gedauert hat, aber auch die Beschreibung der Methoden und der Argumente. Standardmäßig wird der JSON-Bericht unter 'target/jgiven-reports/json' abgelegt; pro Testklasse existiert dann genau ein Bericht. Benötigen wir einen interaktiven HTML-Bericht, so fügen wir unserem Projekt das Maven-Plugin von JGiven hinzu und geben HTML
als Format an:
Listing 6: Maven-Plugin von JGiven
Mittels mvn jgiven:report
erstellt JGiven einen HTML-Bericht aus allen bisher erstellten JSON-Berichten und legt diesen unter 'target/jgiven-reports/html' ab.
Weitere möglichen Formate, die in der Plugin-Konfiguration statt HTML
angegeben werden können, sind text
und asciidoc
. Diese werden dann entsprechend unter 'target/jgiven-reports/text' bzw. 'target/jgiven-reports/asciidoc' erstellt. Die text
-Variante beinhaltet wiederum nur die einzelnen textuellen Beschreibungen der JGiven-Tests, die asciidoc
-Variante ist umfassend und eignet sich hervorragend als Teil der Test-Dokumentation.
Dann: Wir haben Testberichte für verschiedene Zwecke
Die JGiven-Reports sind einfach lesbar und dokumentieren verständlich, was genau getestet wurde und welche Anforderungen abgedeckt sind. Sie können von entwicklungsfernen Stakeholdern besser verstanden und diskutiert werden. Sie eignen sich gut als Teil der Test-Dokumentation. Aber die Implementierung benötigt einen gewissen Aufwand zur Einarbeitung in die Bibliothek und es entsteht auch einiges an Overhead durch die Aufteilung der Test-Logik in diverse Klassen und Methoden. Die bessere Lesbarkeit der eigentlichen Tests und die Wiederverwendbarkeit der erstellten Stage-Klassen und -Methoden kann den Aufwand jedoch wieder wettmachen.
Die Entscheidung, ob und welche Tests mit JGiven umgesetzt werden sollen, hängt stark von den Anforderungen des Qualitäts-Managements ab. Zur Orientierung kann Folgendes dienen.
JUnit-Report:
- Der Fokus liegt auf der Code-Funktionalität.
- Der Test verifiziert einen eher technischen Schritt (Zinsenberechnung, Update der Daten…); üblicherweise gilt das für einen Unit-Test oder Integrationstest.
- Der Test weicht stark von dem klassischen Given-When-Then-Aufbau ab.
- Der Testbericht ist hauptsächlich für die Entwickelnden interessant und eher weniger für die entwicklungsfernen Stakeholder.
- Der Hauptzweck des Reports dient der Nachverfolgung, welche Tests erfolgreich/nicht erfolgreich sind (als Historie der Entwicklung).
- Anhand des Reports eines fehlgeschlagenen Tests soll festgestellt werden, wo im Code etwas nicht wie erwartet funktioniert (Fokus auf Fehlersuche und Debugging).
JGiven-Report:
- Der Fokus liegt auf der Funktionalität eines Geschäftsprozesses.
- Der Test testet einen geschäftlichen Ablauf oder eine geschäftliche Regel; üblicherweise gilt das für einen Akzeptanztest oder End-to-End-Test.
- Der Test folgt einem klaren Given-When-Then-Szenario (dies ist aber nicht zwingend notwendig für JGiven).
- Der Testbericht muss von jemandem ohne Programmierkenntnisse verstanden werden.
- Der Hauptzweck des Reports dient der Verifikation, dass Anforderungen korrekt verstanden und umgesetzt wurden.
- Anhand des Reports eines fehlgeschlagenen Tests soll festgestellt werden, welcher Prozess-Schritt fehlerhaft ist.
- Der Report soll als Teil der Test-Dokumentation dienen (auch im Prozess der Abnahme).
Seminarempfehlung
Java Programmierung Grundlagen P-JAVA-01
Mehr erfahren