Die Rolle von Tests
Willkommen zu meiner persönlichen Reise durch 12 Jahre als Softwareentwickler. In diesem Rückblick möchte ich meine Entwicklung vom Anfang bis heute teilen, mit besonderem Blick auf die prägende Rolle automatisierter Tests. Mit „Tests“ im Titel sind genau die automatisierten Tests gemeint, die meine Arbeitsweise und die Qualität meiner Software stark beeinflusst haben. Lass uns gemeinsam auf die Höhen und Tiefen dieser Reise schauen: wie ich Herausforderungen gemeistert habe und was ich unterwegs gelernt habe.

Meine Reise als Entwickler begann vor 12 Jahren. Rückblickend war meine Art, Code-Komponenten zu entwickeln, ziemlich katastrophal. Ausgehend von einer mündlichen Beschreibung einer Funktion überlegte ich, wie ich sie als Programm umsetzen könnte. Nach den ersten Überlegungen fing ich direkt an, die Komponenten zu bauen. Sobald keine Syntaxfehler mehr zu finden waren, kompilierte ich das gesamte Softwareprojekt und startete es lokal auf meinem PC. Ich navigierte durch die Benutzeroberfläche zu der Stelle, an der das Feature genutzt wurde, und klickte herum. Trat ein Fehler auf, sah ich in die Logdateien, analysierte den Stacktrace und versuchte, den Fehler zu beheben. Dieser Kreislauf aus Code schreiben, Software kompilieren, durch die Oberfläche navigieren und Fehler suchen wiederholte sich, bis das Feature zu funktionieren schien und keine Fehler mehr auftraten.
Nach einigen Monaten Erfahrung in Entwicklung und Betrieb unserer Anwendung erkannte ich Verbesserungspotenzial. Viele Fehler kamen daher, dass sich eine Bibliothek anders verhielt als erwartet. Die Idee war, unser Wissen über die eingesetzten Bibliotheken zu vertiefen, um falsche Annahmen zu reduzieren. Ich begann, den Quellcode jeder Bibliothek zu studieren und zu verstehen, wie sie funktioniert. Ein paar Monate später zeigte sich, dass sich die Arbeit auszahlte. Es traten weniger Fehler auf, und wenn doch, ließen sie sich schneller finden und beheben.
Diese Verbesserung machte ein anderes Problem deutlicher. Der Wissensstand in unserem Team war sehr unterschiedlich. Um uns in den Projekten gegenseitig unterstützen zu können, musste jeder Kollege alle Annahmen und das Verhalten der verwendeten Bibliotheken kennen. Lange Übergabemeetings waren nicht effektiv, und vieles von dem, was besprochen wurde, war schnell vergessen. Die Umsetzung enthielt oft Fehler, die der verantwortliche Kollege in der Testphase korrigieren musste. Die Unterstützung durch einen Kollegen führte am Ende zu einem Vielfachen der Umsetzungszeit. Im Rückblick lautete das Fazit: Hätte ich doch alles selbst gemacht. Aus dieser Erfahrung wuchs eine Erkenntnis: Wir müssen davon ausgehen, dass Menschen Fehler machen.
Meine Projekte begannen immer mit einem Problem des Kunden. Stellte sich heraus, dass sich das Problem am besten mit Software lösen ließ, landete es auf meinem Tisch, zusammen mit einer vermeintlich fertigen Lösung. Sätze wie „Mach es so und so, dann haben wir das Problem gelöst“ beruhigten mich anfangs. Der Berater wusste genau, was gebraucht wurde. Mit den Jahren brach mir aber jedes Mal der kalte Schweiß aus, wenn ich so etwas hörte. Warum, fragst du? Ich versuche, das mit folgendem Beispiel zu veranschaulichen:
Der Kunde hat einen Termin mit unserem Berater. In diesem einstündigen Termin erklärt der Kunde sein Problem. Der Berater hat einen Vorschlag, der klingt, als könnte er das Problem lösen. Ein paar Tage später habe ich ein Meeting mit dem Berater, in dem er mir die grundlegenden Aspekte der benötigten Software erklärt. Auf Basis dieser Informationen beginne ich mit der Entwicklung. In den nächsten drei Monaten habe ich ab und zu kurze Meetings mit dem Berater, um den Fortschritt abzustimmen. Immer mit demselben Ergebnis: Es geht in die richtige Richtung, mach weiter. Dann kommt der Tag, an dem alle geforderten Funktionen umgesetzt und manuell getestet sind. Die Software funktioniert. Die Lösung wird dem Kunden vorgestellt. Nach zwei Minuten sagt der Kunde: „Das ist überhaupt nicht das, was ich brauche.“
Dieses extreme Beispiel soll Folgendes zeigen: Am Ende des Projekts wird die Software nicht das sein, was zu Projektbeginn gedacht war. Wie groß diese Abweichung ausfällt, lässt sich beeinflussen. Das ist aber nicht Thema dieses Artikels. Die Software muss sich verändern können. Änderungen werden kommen und sind der Normalfall.
Genau diese Veränderbarkeit lässt sich mit meiner ursprünglichen Art der Softwareentwicklung nicht abbilden. Bei jeder Änderung müsste ich wissen, welche Auswirkungen sie auf die anderen Komponenten der Software hat. Da Menschen ziemlich vergesslich sind, passieren früher oder später Fehler. Um hier auf Nummer sicher zu gehen, müssten alle Aspekte der Software erneut manuell getestet werden. Dafür muss das entsprechende Verhalten dokumentiert sein, ebenso wie Beschreibungen, welches Verhalten wie zu testen ist. Vom Zeitaufwand für all diese Arbeit ganz zu schweigen.
Über die Jahre habe ich immer wieder über verschiedene Arten automatisierter Tests gelesen. Unit-Tests, Integrationstests, End-to-End-Tests waren für mich theoretische Konzepte und nichts, womit ich in meiner täglichen Arbeit in Berührung kam. Ab und zu hatte ich versucht, automatisierte Tests für meine Komponenten zu schreiben. Es scheiterte aber jedes Mal, weil ich nicht wusste, wie man in unserer Software Tests anlegt. Rückblickend kann ich heute sagen: Das Hindernis für Unit-Tests war unsere Softwarearchitektur. Es waren keine Grenzen definiert, die Komponenten waren mit Verantwortungen überladen. Für einen Unit-Test mussten fast alle Komponenten der gesamten Software initialisiert werden. Das Thema Softwarearchitektur heben wir uns für einen anderen Beitrag auf.

Fast 8 Jahre nachdem ich mit der Softwareentwicklung begonnen hatte, konnte ich nun Tests schreiben. Die positiven Effekte zeigten sich schnell. Nach 8 Jahren ohne Tests hat man einiges an Erfahrung gesammelt und weiß die Vorteile automatisierter Tests zu schätzen:
- Tests sind die Definition von Verhalten: Wenn ich eine Komponente erstelle, weise ich ihr bestimmte Verhaltensweisen zu. Für jedes erwartete Verhalten schreibe ich einen Test, der sicherstellt, dass die Komponente tut, was ich von ihr erwarte. So halte ich meine Gedanken zum Zeitpunkt der Erstellung in Form der Tests fest. Fragen wie „Was war noch mal die Aufgabe dieser Komponente?“ stellen sich nicht mehr. Ich kann mir die Tests ansehen und sehe, was die Komponente tun soll.
- Tests führen zu einer besseren API: Wenn ich früher Komponenten erstellt habe, kam es oft vor, dass ich bei der späteren Nutzung merkte, wie schwer die API zu bedienen war. Seit ich Tests schreibe, merke ich schon beim Erstellen der Komponente, ob die API einfach zu nutzen ist oder nicht. Ich habe einen Anreiz, die API benutzerfreundlich zu gestalten, weil ich gezwungen bin, sie für meine Tests selbst zu nutzen.
- Tests als Leitfaden für Anpassungen: Wer bestehende Software ändert, braucht nicht unbedingt umfassendes Wissen darüber, wie die Software im Detail funktioniert. Die Definitionen, die das Verhalten der Komponenten bestimmen, sind durch Tests umfassend dokumentiert. Verletzt eine Änderung eine ursprüngliche Annahme, weist der fehlschlagende Test den Entwickler gezielt auf das Problem hin. Der Entwickler gewinnt die Sicherheit, dass er nichts kaputt macht.
- Tests decken konzeptionelle Probleme früh auf: Softwareentwicklung ist ein binäres Feld – entweder es funktioniert oder nicht. Bei der Umsetzung von Konzepten und Ideen, die menschlichem Denken entspringen, werden konzeptionelle Hürden früh sichtbar. Diese Probleme früh zu erkennen, verkürzt die Umsetzungszeit erheblich. Das Schreiben von Tests spielt eine entscheidende Rolle dabei, das reibungslose Zusammenspiel der Komponenten zu bewerten.
- Tests helfen beim Skalieren: Software ohne Tests setzt voraus, dass jeder beteiligte Entwickler die Software im Detail kennt. Das führt dazu, dass ein Entwickler viel Zeit braucht, um sinnvolle Code-Beiträge zu leisten. Software mit Tests erlaubt auch weniger erfahrenen Entwicklern, etwas beizutragen, weil das geforderte Verhalten durch Tests abgesichert ist. Weil es Tests gibt, kann ich Arbeitspakete definieren und mit wenig Zeitaufwand an Kollegen übergeben. Die Arbeitsergebnisse des Kollegen lassen sich über seine Tests schnell bewerten. Falls nötig, lassen sich Anpassungen mit minimalem Aufwand vornehmen.
- Tests führen zu einer besseren Softwarearchitektur: Tests für Software mit schlechter Architektur zu schreiben, ist sehr zeitaufwendig. Wenn ich eine Komponente ändere und danach Hunderte oder Tausende Tests anpassen muss, steigt meine Motivation, etwas zu ändern. Wenn es dir schwerfällt, einen Test für eine Komponente zu schreiben, denkst du darüber nach, wie es besser geht. Das führt oft dazu, dass Verantwortungen klarer werden. Tests für Software mit guter Architektur lassen sich schnell und einfach schreiben.
- Tests sind die Grundlage für agiles Arbeiten: Anforderungen an Software ändern sich ständig. Automatisierte Tests helfen beim Anpassen, beim Beheben von Bugs und beim Erweitern. Software besteht aus Tausenden von Komponenten. Jeder Komponente sind Verantwortungen zugewiesen. Ohne automatisierte Tests sind manuelle Tests die Folge. In der Praxis ist das aber nicht machbar.
- Tests helfen, das Verhalten einer Bibliothek oder eines Frameworks zu verstehen: Wenn ich heute eine Komponente aus einer Bibliothek nutze, schreibe ich ein paar Tests, um meine Annahmen zu prüfen. Stellt sich heraus, dass mein Wissen über die Komponente nicht ausreicht und meine Annahmen falsch sind, schlägt der Test fehl. Das gibt mir die Gelegenheit, meine Annahmen anhand der Dokumentation oder des Quellcodes zu korrigieren. Sobald der Test erfolgreich durchläuft, kann ich davon ausgehen, dass ich ein ausreichend korrektes Verständnis aufgebaut habe. Für den Lebenszyklus der Bibliothek in der Software gibt es einen weiteren Vorteil. Verhält sich die Bibliothek in einer neueren Version anders als ursprünglich, schlägt ein Test fehl, und ich als Entwickler habe die Gelegenheit, mich darum zu kümmern. Das gibt mir ein Gefühl von Sicherheit, das mir hilft, die Versionen der verwendeten Bibliotheken aktuell zu halten.
Fazit
Zusammenfassend kann ich sagen, dass automatisierte Tests nicht nur zu höherer Codequalität geführt haben, sondern auch zu einer nachhaltigeren und effizienteren Art, Software zu entwickeln. Die gewonnenen Erkenntnisse haben meine Herangehensweise an Projekte grundlegend verändert und tragen wesentlich zu meinem Verständnis guter Softwarearchitektur bei. Die Entscheidung, Tests als festen Bestandteil meines Entwicklungsprozesses zu etablieren, erwies sich als Schlüssel zu erfolgreicher und zukunftsorientierter Softwareentwicklung.