In unserer monatlichen Blog-Reihe zu unserem Anti-Pattern-Kalender 2014 zum Thema Testing hier der Kandidat für den Monat Juli: Die Versorgungskette.
Wenn einer für den anderen sorgt…
Eine Versorgungkette entsteht, wenn ein Test die Voraussetzungen für den nächsten Test schafft, der wiederum dasselbe für seinen Nachfolger tut und so weiter und so fort. Sehr oft sieht man solche Ketten bei Tests, die einen großen Teil eines Softwaresystems oder sogar das ganze System testen. Nehmen wir als Beispiel das Hotelbuchungssystem, das wir schon in vorangegangenen Folgen dieser Reihe bemüht haben. Es wäre kein Wunder, wenn wir solche Tests finden würden:
- Test: Buche ein Zimmer für den Kunden Müller und überprüfe die erfolgreiche Buchung.
- Test: Erstelle eine Rechnung für den Kunden Müller und überprüfe die korrekte Berechnung des Zimmerpreises, der Steuern usw.
- Test: Simuliere die Bezahlung der erstellten Rechnung und stelle sicher, dass der Umsatz korrekt verbucht wird.
…, dann geht besser nichts schief.
Die Eigenschaften einer solchen Versorgungskette sind:
- Alle Tests sind davon abhängig, dass die vorhergehenden Tests erfolgreich sind.
- Es gibt nur eine festgelegte Reihenfolge, in der die Tests erfolgreich sein können.
Das sorgt für Probleme: So schlagen Tests fehl, wenn wir die Reihenfolge der Ausführung verändern, ohne dass es sich wirklich um einen Fehler handeln würde. Sollte es doch einen Fehler im Produktionscode geben und dieser am Anfang der Kette stehen, dann sind die Tests nicht sehr hilfreich bei der Fehlersuche, da plötzlich eine Menge Tests (nämlich auch alle Nachfolger) fehlschlagen. Schlussendlich sind diese Tests auch nicht parallelisierbar.
Des Pudels Kern
Der Grund für diese Probleme ist: Es gibt einen globalen Zustand (in unserem Beispiel die Persistenz des getesteten Systems), der von Test zu Test bestehen bleibt. Was können wir dagegen tun? Das kommt darauf an: Ist es möglich den globalen Zustand zu umgehen oder nicht?
Falls es nicht möglich oder extrem aufwändig ist, sich des globalen Zustands zu entledigen, muss zumindest sichergestellt werden, dass nach oder vor jedem Test dieser globale Zustand auf einen gültigen Anfangszustand zurückgesetzt wird. Damit sind die Tests in beliebiger Reihenfolge ausführbar.
Am Besten jedoch beseitigt man das Problem von Grund auf, indem man einen globalen Zustand zumindest in den Tests gänzlich vermeidet. So ist es bei Unittests möglich, Singleton-Objekte für die Tests durch entsprechende Test Doubles zu ersetzen. Dann steht auch einer Parallelisierung nichts mehr im Wege und die Trennschärfe zwischen den Tests ist gewährleistet: Wenn die Zimmerbuchungsfunktionalität fehlerhaft ist, dann scheitert nur der erste Test und nicht auch alle seine Nachfolger.