Stirnrunzeln über “Wie viel Sinn machen Unittests?”
Seit Oktober 2009 verfolge ich mit Begeisterung das Streitgespräch zwischen Golo Roden und Peter Bucher. Beide Autoren stellen sich jeden Monat von neuem diversen Fragen und Problemen aus dem Bereich der Softwareentwicklung.
Auch dieses mal kann man wieder über ein Spannendes Thema lesen: Wie viel Sinn machen Unittests?
Diesmal allerdings fühle ich mich gezwungen selber ein Kommentar über den neuesten Beitrag zu schreiben. Denn das erste mal seit dieser Serie bin ich sehr erstaunt, bzw. sogar leicht erschreckt, über Golos Beitrag.
So schreibt Golo, dass Unit Tests zwar wünschenswert sind und und durchaus Sinn machen, allerdings nur an explizit ausgewällten Stellen:
Golo schrieb:
Wie viel Sinn machen Unittests also nun? Zusammengefasst kann man sagen, dass Unittests – an der richtigen Stelle eingesetzt – durchaus Sinn ergeben, dass diese Stellen aber explizit ausgewählt werden sollten.
Des weiteren schreibt er, dass eine 100%ige Testabdeckung zwar wünschenswert ist, aber in der Praxis kaum möglich ist und sogar auf kosten der OOP geht.
Als Begründung nennt er drei Aspekte, auf die ich hier etwas eingehen möchte:
Golo schrieb:
Der bestehende objektorientierte Aufbau ist unter OOP- und stilistischen Aspekten sauber umgesetzt, läuft einer guten Testbarkeit allerdings zuwider.
Diesen ersten Aspekt kann ich leider nicht nachvollziehen, bzw. halte ihn aus meiner Erfahrung heraus sogar für paradox. Im Gegenteil bin ich der Meinung, dass eine saubere, allen Regeln des OOP entsprechende Architektur testbarer ist. Ich gehe sogar noch weiter und behaupte, dass eine mir TDD umgesetzte Anwendung OOP besser umsetzen kann als eine, nicht mit Unit Tests abgedeckte Anwendung.
Denkt man darüber nach was Unit Tests eigentlich sind, nämlich Tests der kleinsten vorhandenen Units, beeinträchtig das keineswegs negativ den aufbau der Anwendung nach allen regeln der OOP. Ein Unit Test testet die kleineste ansprechbare Einheit einer Klasse, das ist eine Methode oder eine Eigenschaft, aber weder deren Abhängigkeiten, noch komplexe Routinen und Szenarien.
Unit Tests unterbinden Abhängigkeiten:
Golo schrieb:
Es bestehen zahlreiche Abhängigkeiten zu externen Komponenten, die sich nicht oder nur mit sehr viel Aufwand simulieren lassen.
Eine saubere Architektur besteht ohne Abhängigkeiten. Mit Hilfe von IoC Containern und Dependency Injection werden Abhängigkeiten gelöst. Dadurch lassen sich die einzelnen Units noch einfacher Testen. Dadurch lassen sich z. B. ohne Aufwand andere spezielle für diese Tests benötigte Bibliotheken laden. Das wiederspricht also schon dem zweiten Aspekt. Beispielswiese lässt sich für den Unit Test eine Library laden, die den Zugriff auf eine Im-Memory-Datenbank ermöglicht, statt auf einen SQL Server Datenbank.
Eine weitere Möglichkeit sich in in Unit Tests von Abhängigkeiten zu lösen sind Mocking Frameworks wie z. B: RhinoMocks:
Golo schrieb:
Es bestehen Abhängigkeiten zur konkreten Laufzeitumgebung, die sich nicht oder nur mit sehr viel Aufwand nachbilden lassen.
Mocking Frameworks werden genutzt um z. B. abhängige Klassen zu simulieren. Anhand eines Interfaces werden “gefälschte” Objekte erzeigt, die lediglich die benötigten Funktionen bereitstellen und die gewünschten werte liefern.
Datenzugriffsklassen, Dateienzugriffsklassen, eigentlich alle Abhängigkeiten lassen sich in einer sauberen Umgebung simulieren und der Unit Test konzentriert sich einzig und allein auf das Unit, statt Abhängigkeiten berücksichtigen zu müssen.
Golo schrieb:
Um Webbrowser-spezifisches Verhalten zu simulieren, müssten die Eigenheiten des jeweiligen Webbrowsers im Unittest nachgebildet werden. Alternativ könnten der entsprechenden gewünschte Webbrowser automatisiert werden – was seinerseits allerdings einen ziemlichen Aufwand nach sich zieht.
Das ist meiner Meinung nach die Aufgabe eines integrationstests, nicht die eines Unit Tests. Da hier ein komplettverhalten getestet wird und nicht das Verhalten einer einzelnen Unit. Um diese Unit zu testen muss der HttpRequest der von der Unit benötigt wird, simuliert werden.
Golo schrieb:
Ebenfalls problematisch ist multithreaded Code, sofern bestimmte Konstellationen in den einzelnen Threads getestet werden sollen – da das Umschalten zwischen den Threads vom aktuellen Kontext des Prozessors beziehungsweise des Betriebssystems abhängt, kann sich das Verhalten von Ausführung zu Ausführung unterscheiden.
Auch hier sollte der Unit Test nicht den Thread testen , sondern die Units die innerhalb der Threads arbeiten, alles andere wäre kein Unit Test.
Mein Fazit:
Die Benutzung von Unit Tests von möglichen Abhängigkeiten “abhängig zu machen” ist in Zeiten mit IOC Containern und Mocking Frameworks nicht mehr haltbar. Mit Hilfe von diesen Hilfsmitteln ist garantiert eine Testabdeckung von annähernd 100% machbar.
Update:
Hier noch ein Statement zum Thema von Thomas Bandt.
Lesenswert sind auch die Kommentare zu Golos Beitrag unter anderem auch von Ralf Westphal.
Mein Fazit (das zweite):
Wie Thomas kann ich bestätigen dass TDD am Anfang sehr, sehr schwierig ist. Wenn man Jahrelang einfach drauf los entwickelt hat, ist es ein wahrer Kraftakt und es erfordert eine menge Konzentration uns Selbstdisziplin Testgetrieben zu entwickeln.