70 VIEWS

Erzeugung von Testobjekten mit dem Builder-Pattern (lesbar und flexibel)

02.10.2020

Tests prüfen und dokumentieren Quellcode. Daher sollten sie sauber und verständlich sein. Die Erzeugung von Testobjekten stört oft den Lesefluss. Insbesondere dann, wenn mehrere Varianten benötigt werden, die sich nur in wenigen Werten unterscheiden. Hier hilft das Builder-Pattern enorm.

Ausgangssituation ohne Builder

Folgendes Listing zeigt einen kleinen Ausschnitt der Instanzierung von Test-Objekten. Man kann sich leicht vorstellen, wie dies mit mehreren Address-Objekten aussieht…

  • Geschwätzig und unübersichtlich. Der Leser tut sich schwer, Initialisierung von Testobjekten von der tatsächlichen Testlogik abzugrenzen. Dies gilt vor allem dann, wenn man mehrere solcher Objekte erzeugen muss, die sich nur in wenigen Attributwerten unterscheiden.
  • Aufwändig, wenn man solche Instanzierungen häufig vornehmen muss. => Man hilft sich manchmal mit Erzeuger-Methoden.
  • Bei diesen besteht jedoch die Gefahr, dass die Parameter-Reihenfolge versehentlich vertauscht wird (auch wenn moderne IDEs hier durch Hints helfen). Richtig hässlich wird es aber, wenn später weitere, leicht veränderte Varianten benötigt werden… 🙁
  • Bei Erzeugung mehrere Testobjekte besteht außerdem die Gefahr, dass man beim Kopieren vergisst, die Referenz anzupassen:

 

Mit einfachem Builder

AddressBuilder UML
generiert via www.plantuml.com
  • Fluent-API fördert die Lesbarkeit und trennt durch Einrückung Objektinitialisierung besser vom restlichen Testcode.
  • Vermeidet die ständige Wiederholung der Variablen address.
  • Verhindert versehentliches Vertauschen der Parameter-Reihenfolge. Zwar helfen hier manche IDEs durch Einblenden der Parameternamen (z.B. IntelliJ), jedoch ist dies dem Lesefluss auch nicht so richtig zuträglich.
  • der Builder kann einfach in anderen Tests wiederverwendet werden. Daher lohnt sich meist der Mehraufwand bei der Erstellung (IDE-Plugins, sowie die mächtigen Such-/Ersetzen-Funktionen moderner IDEs helfen hierbei).

Hinweis: Wir setzen in unserem Fall ein etwas vereinfachtes Builder-Pattern (single field) ein. Außerdem ist unser Builder keine innere Klasse der zu erzeugenden Klasse sondern eigenständig. Dadurch muss die ursprüngliche Klasse aus dem Produktivcode nicht verändert werden. Dies ist wichtig, da der Builder spezielle Hilfsmethoden enthalten soll, die nur für Tests relevant sind und uns das Leben hier vereinfachen. Diese gehören natürlich nicht in den Produktivcode. Häufige Initialisierungen lassen sich auf diese Weise verbergen – wie wir gleich sehen werden.

Mit speziellem Testdaten-Builder

Zusätzlich zu den Vorteilen des einfachen Builders kommt hier Folgendes hinzu:

  • Information Hiding und Fokussierung auf die für den Test relevanten Attribute:
    Wiederkehrendes kann versteckt werden, ohne Lesbarkeit einzubüßen: Die Max-Mustermann-Adresse kommt immer wieder in Tests vor. Dabei kommt es in den Tests hauptsächlich auf die weiteren Attribute an. Mit einem speziellen Testdatenbuilder lassen sich wiederkehrende Initialisierungen, die nur dazu dienen, dass der Code keine Fehler wirft, in Hilfsmethoden verlagern.
    => So kann man Unwichtiges verstecken und die für den Test wichtigen Attribute hervorheben (in diesem Fall sind dies die Attribute email, verified und historic). 🙂
  • Erzeugung unterschiedlicher Repräsentationen möglich:
    Fügt man dem Builder weitere build-Methoden hinzu, lassen sich dadurch ohne großen Aufwand verschiedene Repräsentationen erzeugen. Wir haben dies zum Beispiel in Integrationstests genutzt: Eine build-Methode erzeugt JSON für die Anlage des Testdatensatzes in der NoSQL-Datenbank. Eine weitere generiert das Löschstatement, um die Testdaten wieder aufzuräumen.

Der Testobjekt-Builder sieht in unserem Beispiel wie folgt aus:

 

Fazit

Das Builder-Pattern hilft bei der Instanzierung von Testobjekten. Es erlaubt eine Fokussierung auf die relevanten Attribute. Durch die automatische Einrückung durch die IDE kann man Testobjekt-Instanzierung und Testlogik leichter unterscheiden. Beides führt zu besserer Lesbarkeit der Tests. Der Builder lässt sich außerdem sehr leicht für weitere Tests wiederverwenden – auch dann, wenn Varianten oder komplett unterschiedliche Repräsentationen benötigt werden. Der Mehrwert ist enorm und wiegt den Mehraufwand beim Erstellen des Builders meist auf. 😎

 

👉 Meine Empfehlung ist daher, das Builder-Pattern gleich von Anfang an einzusetzen. Ein nachträglicher Umbau ist aufwändig und macht wenig Spass.

 

Mehr zur Verwendung des Builder-Patterns bei Unit-Tests siehe Folgeartikel Erzeugung von Mocks mit dem Builder-Pattern.

 

Links:

Zurück zur Übersicht

Ein Kommentar zur “Erzeugung von Testobjekten mit dem Builder-Pattern (lesbar und flexibel)

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*