Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».
Benutze immer die Haustür
Ich lese seit einiger Zeit den Google Testing Blog. Da gab's jetzt schon des öfteren gute bekannte Testentwurfs- und -umgangsmuster im markanten Testing on the Toilette-Stil und ich hätte das bislang ohne Einwände unterstützt - bis heuer: Das TotT-Paper Extracting Methods to Simplify Testing kann ich so nicht unkommentiert lassen.
In diesem Paper wird geraten, eine lange und komplexe Methode durch Extrahieren von Submethoden einfacher testbar zu machen. Das Beispiel ist eine Methode zur Berechnung von Testergebnissen, also wieviele Tests fehlgeschlagen oder erfolgreich gelaufen sind. Dabei macht diese Methode drei Dinge:
- Einen Cache nach den Ergebnissen fragen.
- Falls 1. zu keinem brauchbaren Ergebnis geführt hat, eine Datenbank nach den Ergebnissen fragen.
- Ausrechnen, wieviele Tests fehlgeschlagen sind und wieviele erfolgreich durchgelaufen sind.
Now, tests can focus on each individual piece of the original method by testing each extracted method. This has the added benefit of making the code more readable and easier to maintain.Die Methoden sind nicht auf dem gleichen Abstraktionslevel: Die ersten beiden Methoden delegieren die Aktionen (Ergebnisse aus dem Cache holen, Ergebnisse aus der DB holen) an dafür zuständige Objekte (cache bzw. db). Die dritte Methode dagegen berechnet die Anzahl fehlgeschlagener und erfolgreicher Tests (Filtern der Ergebnismenge nach erfolgreichen Tests, Subtrahieren der erfolgreichen Tests von der Ergebnismenge) und delegiert nicht. Damit befindet sich die dritte Methode auf einem niedrigeren Abstraktionslevel, als die beiden anderen Methoden es sind. Angebracht ist hier, eine Klasse zu extrahieren, die die dritte Methode aufnehmen kann. Eine Delegation auf diese neue Klasse würde alle drei Methoden nivellieren auf ein gleiches Abstraktionslevel.
Dadurch, dass die dritte Methode nichts mehr berechnet, entfällt der Nutzen, diese zu veröffentlichen, um sie separat testen zu können, denn die Logik in der dritten Methode existiert jetzt in der extrahierten Klasse und kann von deren Tests geprüft werden. Das Zusammenspiel der vier Objekte (der getesteten Klasse, dem Cache, der DB und der neuen, extrahierten Klasse) ist das eigentlich Interessante und Testwürdige, entweder per Integrationstest oder Unittest, letzterer mit gemockten Kollaborateuren.
Ich habe derartiges Verhalten schon öfter beobachten können, also das Verbreitern der Schnittstelle einer Klasse, damit sie scheinbar einfacher getestet werden kann. Ich habe nie einen Sinn darin erkannt. Auf der XP 2004 hat auf einer BoF-Session ein Teilnehmer ein Pattern vorgestellt, dass ich online leider nicht wiederfinden kann: Always use the Frontdoor. Eine breitere Schnittstelle ist eben nicht einfacher, sondern schwerer zu warten, muss man sich doch um viel mehr Löcher in den Wänden kümmern, als nur vor der Haustür zu fegen. Und lesbarer ist so eine große Schnittstelle auch nicht, denn der Betrachter muss viel mehr Öffnungen im Auge behalten, als er eigentlich bräuchte. Somit hat weder der Entwickler noch der Benutzer einen Vorteil davon.
"Aber das sind doch nur die Tests..." höre ich schon die ersten Unkenrufe. Ja, genau, es sind die Tests: die ersten Benutzer der Klassen und Objekte; die, die zuerst Feedback geben; die, die zuerst signalisieren, dass ihnen etwas stinkt an dem, was ihnen da vorgesetzt wird. Wenn schon die Klagelaute der Tests nicht beachtet werden, wie sensibilisiert man sich dann für das Knirschen im produktiven Gebälk?
Testgetrieben dürfte diese Art von Code auch schwer entstehen, und so ist das nachträgliche Verlangen nach zugänglicheren privaten Methoden fast immer ein Hinweis auf eine zusätzliche Indirektion, z.B. in Form einer Klassenextraktion, mindestens jedoch ein Hinweis darauf, dass man sich testgetrieben ins Aus manövriert hat. Vorher gibt es aber schon Hinweise bei den Tests, nämlich genau dann, wenn die Fixture breiter wird, wenn also ein Cache und eine DB gemockt werden müssen, um die Berechnung der Ergebnisse zu testen, obwohl eigentlich weder Cache noch DB dafür benutzt werden. So entstehen oftmals mehrere Tests innerhalb einer Testklasse, die das gleiche Subset an Objekten der Fixture benutzen. Das deutet auf eine Klassenextraktion hin, sowohl im Test als auch im produktiven Code!
Zusammenfassend läßt sich schreiben: Methoden extrahieren auf gleichem Abstraktionslevel; Abstraktionslevel gegebenenfalls angleichen durch z.B. Klasse extrahieren; keine privaten Methoden veröffentlichen und damit Löcher in die Schnittstellen hauen; auf die Tests hören; Indirektionen bewußt einbauen; Haustüren mit Tests entwickeln.