Beliebte Suchanfragen
//

Test Fixtures mit JUnit 5

25.3.2024 | 7 Minuten Lesezeit

Wir Softwareentwickler leben in einem ständigen Dilemma. Jede Funktionalität der Software sollte durch Unit-Tests und Integrationstest abgesichert werden. Es sollten dabei so viel Tests wie nötig, aber nur so wenige wie möglich geschrieben werden.

Schreiben wir zu wenige Tests, dann sind nicht alle Funktionalitäten unserer Software ausreichend abgesichert. Zu viele Tests bedeuten teure Entwicklungszeit, die wir in unnötige Tests investieren. Da sich die Software im Laufe der Zeit stetig verändert, müssen wir die Tests auch immer wieder anpassen. Mit der puren Anzahl der Tests steigt natürlich auch der Aufwand für die Pflege unserer Testbasis.

Ein recht aufwendiger Teil der Tests ist die Bereitstellung der Testdaten. Daher werden ziemlich schnell standarisierte Testdaten bzw. Test Fixtures in den Projekten genutzt. Ein Vorteil dieser Test Fixtures ist der reduzierte Aufwand bei den Anpassungen. Statt hunderte von Tests zu ändern, müssen nur einige wenige Bereitstellungsmethoden aktualisiert werden.

Für diese Bereitstellungsmethoden werden in den eigenen JUnit 5-Tests diverse Ansätze herangezogen, der Extension-Mechanismus des Frameworks aber häufig außer Acht gelassen. Wie dieser genutzt werden kann, werde ich im Folgenden an einem einfachen Test vorstellen.

Der JUnit 5 Extension-Mechanismus

Der Extension-Mechanismus modifiziert das Verhalten von Testklassen oder -methoden. Vor Junit 5 verwendete JUnit nur die Erweiterungspunkte TestRunner und Rule. JUnit 5 ersetzt diese durch eine vollständige Extension-API. Die API gestattet es an fünf Extension-Points in den Lifecycle eines Tests einzugreifen. In diesem Beitrag nutzen wir den Extension-Point Parameter Resolution, aber dazu kommen wir später.

Der Bibliotheksbenutzer, seine Ausleihe und die Gebühren aka die Domäne

Ein Problem und seine Lösung erklärt man am einfachsten an einem prägnanten Beispiel. Dieses Beispiel bedient sich an meinen IT-Erfahrungen aus der Studentenzeit. Ein Bibliotheksnutzer hat diverse Ausleihen von Büchern und ggf. stehen Gebühren für die Nutzung oder überfällige Medien an.

Ein erster Test

In unserem Beispiel werden die aktuellen Gebühren für einen Nutzeraccount berechnet. Dazu wird ein Account für einen Bibliotheksnutzer erstellt und zwei Ausleihen hinzugefügt. Da eine der beiden Ausleihen überfällig ist, steht eine Mahngebühr von 3 Euro an.

Für den ersten Test eine Menge Code für die Erstellung der Testdaten. Alle weiteren Tests mit ähnlichen Voraussetzungen benötigen ähnliche Codefragmente. Daher sollten wir versuchen diese Fragmente bestmöglich wiederzuverwenden.

Weniger Boilerplate-Code

Für weitere Tests in der Klasse LibraryTest zentralisieren wir das Erstellen der Testdaten. Die bekannteste Möglichkeit dafür ist eine mit @BeforeEach annotierte Methode.

Nun können sich alle Testmethoden die Text Fixture teilen. Da die setUp-Methode vor jedem Test aufgerufen wird, werden diese Daten leider auch für Tests erstellt, die sie gar nicht benötigen.

Parameter Resolver in JUnit 5

JUnit 5 erlaubt es, Parameter an Test und Lifecycle-Methoden zu übergeben. Dafür können eigene ParameterResolver implementiert werden.

Der PatronParameterResolver ersetzt alle Methodenparameter vom Typ Patron mit der Instanz, die von der Methode resolveParameter erzeugt wird. Wir dürfen nicht die @ExtendWith Annotation vergessen, sonst weiß JUnit 5 nicht, woher der Patronkommen soll.

Die Test-Klasse verkürzt sich nun um den vom PatronParameterResolver generierten Kunden.

Der Bibliotheksnutzer wird nun nur noch für die Testmethoden erzeugt, die diesen auch tatsächlich benötigen. Nur schade, dass es immer derselbe Nutzer ist. Kann man das nicht ändern?

Variationen mit Annotationen

Eine Annotation am Parameter kann durch den Parameter Resolver ausgewertet werden und zur Modifizierung der Werte genutzt werden. Mit der Annotation @Fixture sind dann die folgenden Tests möglich.

Der PatronParameterResolver entscheidet mit Hilfe der @Fixture Angabe, welcher Bibliotheksnutzer erstellt werden soll. Der zweite Bibliotheksnutzer bekommt geringere Mahngebühren, weil er noch ein Kind ist.

Wir verwenden an dieser Stelle Personas, um unseren Tests, im wahrsten Sinne des Wortes, ein Gesicht zu geben. Die Persona Jens Kaiser ist ein älterer Herr, der gelegentlich die Bibliothek besucht und Christopher Robin ist ein kleiner Junge mit einem Teddybär.

Das geht auch generisch

Häufig hat man es nicht nur mit einer Domänenklasse zu tun, sondern mit sehr vielen. Daher kann ein Großteil der Implementierung in einer generische Basisklasse verschoben werden.

Der PatronFixtureParameterResolver erbt von dem AbstractFixtureParameterResolver. Dieser erzeugt nicht nur eine Instanz der gewünschten Klasse, sondern beherrscht auch Collections und Arrays dieser Klasse und wertet die @Fixture Annotation aus. Der PatronFixtureParameterResolver kümmert sich nur noch um die Erzeugung der Varianten.

Mehrere Parameter-Resolver

Es geht natürlich auch mit mehreren ParameterResolvern. Neben dem PatronFixtureParameterResolver wird im folgenden Test auch ein BookingFixtureParameterResolver verwendet. Dieser erzeugt die Ausleihen für den Benutzeraccount.

Der BookingFixtureParameterResolver generiert diverse Varianten von Booking-Instanzen. In der Testklasse werden keine Test Fixture mehr erzeugt. Alle Test Fixtures kommen nun aus Parameter Resolvern.

Fazit

Mit dem Parameter Resolver Extension Point bietet JUnit 5 einen mächtigen Mechanismus, um Test-Methoden mit Test Fixtures zu versehen. Wir konnten sehen, dass eigene Parameter-Resolver ohne besonderen Aufwand erstellt und eingebunden werden können.

Die Testdatenerzeugung mit Parameter-Resolvern bringt einige Vorteile. Der Mechanismus ist integraler Bestandteil des JUnit 5 Frameworks. Er ist etabliert, dokumentiert und bietet eine uniforme Lösung für die eigene Datenbereitstellung. Die Testdaten werden nur punktuell erzeugt und verwendet. Nur dort, wo sie als Parameter einer Testmethode angegeben sind, werden sie auch tatsächlich instanziiert. Außerdem können Variationen in den Testdaten über Annotationen direkt an den Parametern der Testmethoden konfiguriert werden.

Für alle, die neugierig geworden sind: einfach mal testen!

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//
Jetzt für unseren Newsletter anmelden

Alles Wissenswerte auf einen Klick:
Unser Newsletter bietet dir die Möglichkeit, dich ohne großen Aufwand über die aktuellen Themen bei codecentric zu informieren.