7 Gründe für Unit Tests in Brownfield-Projekten
Testautomatisierung in Brownfield-Projekten
Brownfield-Projekte, die bereits einige Jahre auf dem Buckel haben und oft eine gewachsene, eng gekoppelte Struktur aufweisen, kämpfen oft mit unerwünschten Querauswirkungen im Zuge der Entwicklung. Ein probates Gegenmittel ist die Automatisierung von Anwendungstests, um diese frühzeitig zu erkennen und eine größere Testabdeckung zu erreichen.
Bei der Ausarbeitung einer passenden Strategie zur Testautomatisierung kommt jedoch oft die Sichtweise auf, auf Unit Tests zu verzichten, besser direkt an der Oberfläche anzusetzen und so den gesamten Stack zu testen. Dies ist kaum verwunderlich, führt die konsequente Erstellung von Unit Tests doch sowohl zu Anpassungen in der Arbeitsweise als auch in der Anwendungsarchitektur – vor allem vor letzterem schrecken viele in Brownfield-Projekten zurück.
Blickt man jedoch auf die Vorteile, die Unit Tests mitbringen, so wird klar, dass diese ein wesentlicher Baustein der Testautomatisierung sind:
#1 - Wiederholbares Debugging
Bisher habe ich noch keinen Entwickler kennengelernt, der den Code ungeprüft in den Einsatz gibt. Meist kommt hierbei das Debugging zum Einsatz, bei dem der Code Schritt für Schritt durchlaufen wird. So kann man schnell erkennen, ob der Algorithmus so abläuft wie vom Entwickler angenommen.
Betrachtet man den Debugging-Vorgang etwas genauer, so fällt auf, dass oft ein beträchtlicher Teil der Zeit dafür aufgewandt werden muss, um die Umgebung herzustellen, also beispielsweise durch die Anwendung zu navigieren, um zur getesteten Stelle zu gelangen. In dieser Hinsicht ist es deutlich effizienter, in einem Unit Test direkt ansetzen zu können.
Im Gegensatz zum Debugging können Unit Tests wiederholt ausgeführt werden. So wird auch für die Zukunft sichergestellt, dass der Algorithmus noch gemäß den Erwartungen entspricht.
Auch wenn Unit Tests sicherlich nicht alle Debugging-Vorgänge ersetzen können, so liegt auf der Hand, dass Entwickler, die Unit Tests erstellen signifikant weniger Zeit ins Debugging investieren müssen.
#2 - Einfache und schnelle Ausführung
Im Gegensatz zu Oberflächen- und Integrationstests bestehen bei guten Unit Tests keine Abhängigkeiten zur Anwendungsinfrastruktur, also etwa Datenbanken oder Dienstschnittstellen. Dadurch können sie auf jeder Umgebung ausgeführt werden, typischerweise auf dem Entwicklerrechner und im Buildprozess.
Dadurch, dass die Einrichtung einer passenden Umgebung nicht notwendig ist und die Tests direkt als Code vorliegen, können sie sehr schnell ausgeführt werden. Dies trägt maßgeblich zur häufigen Ausführung bei. Würde die Ausführung sehr lange dauern, liegt auf der Hand, dass im Eifer des Gefechts kaum ein Entwickler die Tests ausführen würde. Erst recht nicht praktikabel wären Techniken wie das Live Unit Testing, das Visual Studio seit der Version 2017 in der Enterprise Edition unterstützt. Dadurch werden Unit Tests während der Entwicklung direkt ausführt, so dass der Entwickler eine sofortige Rückmeldung während der Entwicklung erhält.
#3 - Direktes Feedback
Im Gegensatz zu anderen Testarten werden Unit Tests sehr häufig ausgeführt. Dadurch erhält ein Entwickler ein direktes Feedback zu den Auswirkungen der getätigten Änderungen. Auch ohne die Rule of ten heranzuziehen ist klar, dass ein Problem wesentlich schneller behoben werden kann, wenn es noch frisch im Kopf ist.
#4 - Feingranulare Aussage zur Fehlerursache
Ein Vorteil von automatisierten Oberflächen- oder Integrationstests – nämlich der große abgedeckte Bereich – ist gleichzeitig ein Nachteil, wenn man den Vergleich zu Unit Tests zieht. Dadurch, dass Unit Tests auf eine einzelne Einheit beschränkt sind, ist bei einem Fehlschlag der Bereich, in dem das Problem liegt, einfach identifiziert.
Idealerweise ist der fehlgeschlagene Test auch so benannt, dass bereits im Test Explorer ersichtlich ist, was die Ursache ist. Auch dies spart wertvolle Zeit bei der Korrektur.
#5 - Positiver Effekt auf die Codestruktur
So banal es klingt: Wer Units testen will, benötigt zuerst einmal Units. Die Erstellung von Unit Tests animiert daher zu einer sinnvollen Strukturierung des Quelltexts in feingranulare Einheiten, weil gut strukturierter Code wesentlich einfacher zu testen ist.
Außerdem nimmt der Entwickler dadurch ganz natürlich eine Außenperspektive auf den erzeugten Code ein. Dies macht den Code deutlich einfacher wiederverwendbar und stabiler.
#6 - Freiheit für Refactorings
Auch wenn die Codestruktur noch nicht optimal ist, sind Unit Tests eine Voraussetzung, ohne die Refactorings kaum ohne Kollateralschäden ablaufen können. Dies hat auch einen psychologischen Effekt, der gerade in Brownfield-Projekten zum Tragen kommt. Oft werden Änderungen abgelehnt oder hinausgezögert, um keine neuen Probleme zu verursachen. Dabei sind gerade in diesen Projekten Umstellungen wichtig, um echte Fortschritte in der Codestruktur zu erreichen. Auch wenn die ersten automatisierten Tests in derartigen Umgebungen meist nicht den Namen „Unit Test" tragen sollten, so reduzieren sie das Risiko von Querauswirkungen enorm. Sind sie dann auch schnell ausführbar, so steht dem Refactoring nichts mehr im Weg. Echte Unit Tests werden dann Stück für Stück mit der verbesserten Codestruktur eingeführt.
#7 - Aufwandsarme Erstellung und Änderung
Klar, Unit Tests zu erstellen, verursacht vor allem am Anfang Aufwand. Diesem steht jedoch im Bereich der Entwicklung der gesparte Debugging-Aufwand gegenüber. Außerdem werden die Aufwände mit steigender Übung geringer.
Auch darf man sich nicht täuschen lassen: Die Erstellung und Pflege von Oberflächentests verursacht ebenfalls signifikante Aufwände, die jedoch mitunter nicht von den Entwicklern sondern vom QA-Team getragen werden. Diese sind zumeist deutlich höher im Vergleich zur Erstellung und Pflege von Unit Tests. Martin Fowler begründet dies sehr anschaulich in seinem Blogbeitrag „TestPyramid".
Unstrittig ist mittlerweile, dass das Investment in automatisierte Tests in Hinblick auf die Qualität und Entwicklungsproduktivität sehr sinnvoll ist. Wie bei jedem Investment spielt auch der Zeitpunkt, ab dem Nutzen abgeschöpft werden kann, eine zentrale Rolle. Dies ist bei Unit Tests durch die häufige und schnelle Ausführung auf verschiedenen Umgebungen deutlich früher als bei anderen Testarten.
Fazit
Natürlich machen Unit Tests für sich alleine noch keine vollständige Strategie zur Testautomatisierung aus, aber die angeführten Vorteile zeigen, dass sie ein wesentlicher Bestandteil dieser Strategie sind. Auch wenn Oberflächentests den gesamten Stack testen, sollten Unit Tests ohne Frage im normalen Entwicklungsvorgehen erstellt werden und auch in Brownfield-Projekten Stück für Stück ausgebaut werden.