© best_vector/Shutterstock.com
Behaviour-driven Development mit Machine.Specifications

Spezifizieren statt testen


Testautomatisierung ist aus der modernen Softwareentwicklung nicht wegzudenken. Um damit aufzuhören, Tests zu schreiben und damit zu beginnen, seine Spezifikationen zu „automatisieren“, muss man nur leicht an einigen Stellschrauben drehen. Dadurch entsteht dann eine lebende Dokumentation, die nicht nur den Ist-, sondern auch den Sollzustand der Software beschreibt.

Testen muss man immer, egal ob automatisiert oder manuell. In jedem Fall ist notwendig zu prüfen, ob die jüngst vorgenommenen Änderungen auch das gewünschte Ergebnis erzielt haben. Und genau hier liegen die eigentlichen Vorteile der Testautomatisierung gegenüber der manuellen Ausführung: Die Tests können immer wieder unter den gleichen Bedingungen durchgeführt werden und ihre Ergebnisse liegen, nach einmaligem Erstellen, innerhalb kürzester Zeit vor. Aufgrund dieser schnellen Wiederholbarkeit adressieren Sie dann nicht mehr nur die direkten Auswirkungen einer Änderung, sondern auch die indirekten. Denn während beim manuellen Testen die Ausführung starken Ressourcenbeschränkungen unterliegt und somit immer nur ein kleiner Bereich der Software zeitgleich geprüft werden kann, erlauben automatisierte Tests die Prüfung der Gesamtanwendung, wann immer es notwendig ist.

Für einige Entwickler mag sich diese Aussage etwas zu positiv anhören und tatsächlich reicht es nicht aus, einfach nur einen Test zu automatisieren und dann auf dessen Ergebnisse zu warten. In aller Regel bedarf es einer Vielzahl von Tests, um eine so hohe Abdeckung des Codes zu erreichen, dass die oben genannten Ziele auch tatsächlich zur Zufriedenheit erreicht werden. Darüber hinaus ist die Testautomatisierung, genauso wie die generelle Programmierung, eine Kunst, die erst erlernt werden möchte. Schnell tappt man gerade beim Verfassen von Unit Tests in Fehler, die das spätere Verstehen und Anpassen jener Tests erschweren. Da wäre es doch hilfreich, wenn uns die Frameworks etwas mehr an die Hand nehmen würden, um zu vermeiden, dass wir versehentlich den Pfad der Tugend verlassen.

Die Krux mit den Unit-Test-Frameworks

Frameworks wie MS Test oder NUnit werden gerne als „XUnit-Frameworks“ zusammengefasst und ähneln sich in der Nutzung auch tatsächlich sehr stark. Wie Listing 1 zeigt, werden die Testfälle in aller Regel als Methoden innerhalb von Klassen realisiert. Dabei gruppieren die Testklassen eine Vielzahl von Testfällen, die den gleichen Testgegenstand haben. Hier tritt auch nicht selten schon das erste Missverständnis auf. Denn da es als allgemeine Praxis zu gelten scheint, nur eine einzige Testklasse für jede zu testende Klasse anzulegen, und da jene genauso benannt wird wie die zu testende Klasse (nur erweitert um das Postfix „Test“), steigt nicht selten die Komplexität. Zugegebenermaßen: Wenn man all seine Testfälle so zusammenführt, scheint es zumindest so, als wäre deren Realisierung leichter verständlich. Nichtsdestotrotz kann diese Vorgehensweise Auslöser für ein echtes Problem sein. Denn nutzt man für alle Testfälle nur eine Klasse, gibt es im Grunde genommen auch nur eine Initialisierungsmethode. Solche Methoden werden bei MS Test mit Initialize und bei NUnit mit SetUp gekennzeichnet. Ihre Aufgabe ist es, typischen Initialisierungscode vor der Ausführung jeder einzelnen Testmethode zusammenzufassen, um Codedopplungen zu vermeiden. Unglücklicherweise finden sich hier nicht selten Initialisierungen, die nur von einzelnen Tests gebraucht, aber für alle ausgeführt werden. Auf diese Weise entsteht dann ein Overhead, der die Ausführung der Tests verlangsamt und beim Debugging sehr behindern kann.

Listing 1 zeigt dies ganz deutlich. Dort wurden anfangs vor allem Tests geschrieben, die den Speichervorgang bei einem ViewModel prüfen sollten. Später wurde die Klasse aber noch um Tests zum Löschen von selektierten Objekten erweitert, für die nun trotz allem noch jeweils ein neues Personenobjekt erstellt wird. Diese ungünstige Konstellation kann auf zwei Wegen gelöst werden: Entweder die Tests für das Szenario des Löschens erhalten eine eigene Testklasse und damit eigenen Initialisierungscode oder die SetUp-Methode wird bis auf die ersten beiden Zeilen gekürzt und der Rest z. B. über eine Helfermethode innerhalb jeder Testmethode erneut ausgeführt.

Last but not least sind vor allem die Testmethoden selbst ein häufiges Ziel für Diskussionen. Sowohl ihre Namensgebung als auch ihr innerer Aufbau entscheidet maßgeblich darüber, in welchem Umfang die Tests später helfen oder behindern. Ist dabei der Name der Methode nicht aussagekräftig und/oder werden die Assert-Aufrufe nicht mit einer beschreibenden Nachricht versehen, ist später nicht mehr ersichtlich, warum der Code überhaupt verfasst wurde.

Listing 1: Eine schlechte ...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang