Approval-Tests als Refactoring-Sicherungsnetz

20.10.2023

Wir haben Bestandscode, der eine zu niedrige Testabdeckung hat, um sicher ein Refactoring durchführen zu können (in unserem Fall war das die Ablösung einer Dependency durch eine eigene Implementierung). Was tun?

Hier sind Approval-Tests sehr hilfreich. Im Java-Umfeld bietet sich die Bibliothek ApprovalTests.Java an.

Diese lässt sich bei einem Projekt mit Maven ganz leicht als Dependency mit Test-Scope einbinden:

Nehmen wir an, wir haben eine Klasse, welche aus einer Liste von Datensätzen eine neue gruppierte Liste erzeugt. Nun schreiben wir einen Test, welcher eine Beispieldatei einliest und diese an die Businesslogik übergibt. Danach rufen wir Approvals.verify auf.

Hinweise:

  1. Falls die Reihenfolge der Datensätze im Ergebnis variieren kann, z. B. weil sie parallel erzeugt werden, müssen wir den Stream natürlich noch sortieren, sodass wir nicht jedes Mal ein anderes Ergebnis bekommen.
  2. Falls die toString-Methode in den Objekten nicht oder nicht ausreichend implementiert ist, können wir auch ReflectionToStringBuilder::toString aus der Apache Commons Lang Bibliothek verwenden. Dabei müssen wir nur einen ToStringStyle verwenden, der keine Objektreferenzen ausgibt, da sich diese mit jeder Ausführung ändern. Alternativ lassen sich diese Objektreferenzen auch mittels Scrubber bereinigen (siehe weiter unten).

Wenn wir den Unit-Test nun ausführen, erzeugt die Bibliothek im gleichen Verzeichnis eine Textdatei mit dem folgenden Namensmuster:

Testklassenname_Testmethodenname.received.txt

Diese enthält das erhaltene Ergebnis. Nun nimmt man eine Sichtprüfung vor. An dieser Stelle kann man auch den Kunden einbinden und sowohl Eingabedatei als auch Ergebnis abstimmen. Wenn das Ergebnis mit der bisherigen Logik passt, benennt man die Datei um:

Testklassenname_Testmethodenname.approved.txt

Das erzeugte Ergebnis wurde damit für gut befunden / genehmigt.
Führt man den Test erneut aus, wird das neue Ergebnis immer gegen diese Datei geprüft. Falls sich Änderungen ergeben, bekommt man wieder eine Datei mit der Endung „.received.txt“. So kann man beide Dateien in einem Diff-Tool miteinander vergleichen. Dies ist sehr hilfreich, da wir so beim Refactoring schnell sehen, wenn wir jetzt etwas kaputt machen.

Die Bibliothek bietet aber noch mehr Möglichkeiten. So lässt sich z. B. mittels JsonJacksonApprovals.verifyAsJson ein Objekt automatisch nach JSON konvertieren und dann auf dieselbe Weise wie oben dargestellt vergleichen. In unserem Fall ist das eine baumartige Datenstruktur:

Umgang mit Objektreferenzen, Zufallswerten, GUIDs, Zeitstempel etc.

Doch was ist, wenn unsere Businesslogik Daten ausgibt, die sich bei jedem Lauf verändern, z. B. einen Zufallswert oder aktuelle Zeitstempel?

Die beste Lösung wäre hier, dass wir diese Teile der Businesslogik so bauen, dass sie im Test mit festen Werten ersetzt werden können. Für Datums- und Zeit-bezogene Werte bietet sich hier der Einsatz einer Clock an. Diese kann im Test dann mit einer Implementierung ersetzt werden, die immer denselben Wert liefert (Clock.fixed).

Falls sich dies nicht bewerkstelligen lässt, z. B. weil die Werte aus einer Thirdparty-Klasse kommen, lässt sich bei Approval-Tests ein sogenannter Scrubber einsetzen. Mehr dazu findet sich in der verlinkten offiziellen Dokumentation.

 

Fazit

Approval-Tests sind tolles Werkzeug. Neben dem klassischen Anwendungsfall, um Ergebnisse mittels Sichtprüfung mit dem Kunden abzustimmen, können sie auch als Refactoring-Sicherungsnetz genutzt werden, da sie das komplette Ergebnis prüfen.

Links


Bildnachweis: Bild „Sicherheitsnetz auf Baustelle“ von 13810164 auf Pixabay

Zurück zur Übersicht

Kommentar verfassen

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

*Pflichtfelder

*