
Ein Gespräch über Git und warum gutes Testing heute wichtiger ist denn je.
Letzten Monat hatte ich ein aufschlussreiches Gespräch mit meinem Team darüber, wie wir Git verwenden. Ich ging davon aus, dass alle über das Terminal arbeiten, schliesslich verwalten wir Updates in 14 verschiedenen Repositories. Doch es stellte sich heraus: Ich war der Einzige. Die anderen zehn Entwicklerinnen und Entwickler nutzten ausschliesslich die Git-Oberfläche ihrer IDE.
Das brachte mich zum Nachdenken: Wie viele andere vermeintlich „selbstverständliche“ Praktiken sind es in Wirklichkeit nicht? Testing ist eine davon. Viele Entwicklerinnen und Entwickler wissen, dass sie Tests schreiben sollten, aber nur wenige verstehen wirklich, wie und warum. Besonders jetzt, da KI unsere Art zu programmieren verändert. Genau deshalb möchte ich einige bewährte Testing-Praktiken teilen.
KI-gestützte Programmierung verändert das Spiel: Testing ist Ihr Sicherheitsnetz
KI-Assistenten wie GitHub Copilot oder ChatGPT können Code in beeindruckender Geschwindigkeit generieren, aber sie bringen versteckte Risiken mit sich. Sie „verstehen“ weder die Anforderungen noch die Rahmenbedingungen eines Systems wirklich. Sie liefern Lösungen, die auf den ersten Blick korrekt wirken, aber wichtige Randfälle ignorieren, Fehlerbehandlung vernachlässigen oder geschäftskritische Logik übersehen können.
Dadurch entstehen verborgene Risiken, insbesondere bei Refactorings, wenn unklar bleibt, ob Änderungen subtile Fehler einführen.
Testing ist in diesem neuen Paradigma unser zentrales Sicherheitsnetz. Es zwingt uns, festzulegen, was als korrektes Verhalten gilt, mögliche Fehlerszenarien zu antizipieren und sicherzustellen, dass die Integrität unseres Codes auch bei Änderungen erhalten bleibt. Die Tests, die wir schreiben, kompensieren die blinden Flecken der KI und verwandeln generierten Code von einem potenziellen Risiko in ein verlässliches Asset.
Werfen wir also einen genaueren Blick darauf, wie sich KI-generierter Code testen lässt.
Testing trennt „Es funktioniert“ von „Es ist korrekt“
KI kann das Tippen automatisieren, aber nicht das Denken. Deshalb sind Testing-Fähigkeiten heute wertvoller denn je. Die Teams, die mit KI erfolgreich sind, sind nicht diejenigen, die den meisten Code schreiben, sondern jene, die ihn am besten validieren.
Im Zeitalter von KI ist es genauso wichtig zu verstehen, warum Code funktioniert, wie ihn überhaupt zu schreiben. Doch was macht einen guten Test aus?
Was ist eigentlich ein Test?
Um zu verstehen, was einen guten Test ausmacht, müssen wir zuerst definieren, was Testing bedeutet:
Software-Testing umfasst Techniken, mit denen überprüft wird, ob ein entwickeltes System die definierten Anforderungen erfüllt.
Tatsächlich testen wir alle unseren Code – zumindest manuell. Doch manuelles Testing ist teuer, langsam, fehleranfällig und schwer reproduzierbar. Deshalb ist automatisiertes Testing unverzichtbar.
Automatisierte Tests helfen, Entwicklungsgeschwindigkeit zu erhöhen und Wartungsprobleme zu vermeiden – vorausgesetzt, sie sind sinnvoll umgesetzt. Das Schreiben von Tests ist eine Investition, deren Wert von ihrer Qualität abhängt. In diesem Artikel betrachten wir die Eigenschaften, die gute Tests auszeichnen.
Funktionales vs. nicht-funktionales Testing
Grundsätzlich lassen sich Tests in zwei Kategorien einteilen:
Funktionales Testing prüft, was das System tut. Dazu gehören:
Unit Tests (isolierte Komponenten)
Integrationstests (Zusammenwirken von Komponenten)
End-to-End-Tests (E2E, Simulation ganzer Nutzerabläufe)
Regressionstests (Überprüfung bestehender Funktionalität)
Nicht-funktionales Testing prüft, wie das System funktioniert, zum Beispiel in Bezug auf:
Performance
Sicherheit
Barrierefreiheit
Usability
Wartbarkeit
Während funktionale Tests meist von Entwicklerinnen und Entwicklern verantwortet werden, benötigen nicht-funktionale Tests oft spezialisierte Expertise.
Testverteilung: Die sich wandelnde Testpyramide
Ein bekanntes Modell ist die Testpyramide, die folgendes Verhältnis empfiehlt:
Viele Unit Tests (schnell, leicht zu schreiben)
Weniger Integrationstests (langsamer, komplexer in der Wartung)
Noch weniger E2E-Tests (am langsamsten, am anfälligsten)
Das Modell wurde 2009 von Mike Cohn vorgestellt und entwickelt sich immer weiter. Moderne Tools wie Playwright oder Cypress machen Integrationstests und E2E-Tests deutlich praktikabler als frühere Lösungen wie Selenium. Gleichzeitig reduzieren statische Analysen (z. B. Linter, TypeScript) den Bedarf an defensiven Tests, sodass der Fokus stärker auf der Business-Logik liegen kann.
Es gibt keine universelle Teststrategie; jedes Projekt braucht seine eigene Balance. Entscheidend ist das richtige Verhältnis aus Geschwindigkeit, Abdeckung und Wartbarkeit bei optimaler Nutzung moderner Tools.
Was sollte getestet werden?
Eine der grössten Herausforderungen beim Testing ist zu wissen, was man testen soll. Ohne hier zu tief einzusteigen, möchte ich eine wichtige Lektion teilen, die meine Sicht auf Tests grundlegend verändert hat.
Die Falle des „Alles-Testens“
Als ich zu einem Projekt mit kaum vorhandenen Unit Tests kam, schien die Lösung klar: mehr Tests schreiben. Also taten wir das, mit massivem Mocking, rund 90 % Coverage und grossem Stolz. Doch bald zeigte sich die Realität:
Refactorings zerstörten Tests, obwohl sich das Verhalten nicht geändert hatte.
Hohe Coverage vermittelte trügerische Sicherheit – Fehler schlichen sich trotzdem ein.
Übermässiges Mocking machte Tests fragil und realitätsfern.
Der eigentliche Fehler: Wir testeten Implementierungsdetails, nicht das Verhalten aus Nutzersicht.
Ein besserer Ansatz
Inspiriert von Autorinnen und Autoren wie Kent C. Dodds änderte ich meine Perspektive und formulierte diese Grundsätze für Unit Tests:
Testen Sie, wie ein Nutzer: Konzentrieren Sie sich auf das Was, nicht auf das Wie.
Vergessen Sie starre Kategorien: Der Wert eines Tests liegt nicht in „Unit“ oder „Integration“, sondern darin, Fehler früh zu erkennen.
Setzen Sie Prioritäten: Testen Sie relevante Szenarien, nicht nur isolierte Einheiten.
Coverage-Berichte ≠ Qualität
Deshalb bin ich gegenüber Coverage-Metriken skeptisch: Sie werden oft zum Selbstzweck. Der eigentliche Sinn von Tests ist nicht, eine Zahl zu erreichen, sondern die Frage zu beantworten:
„Würde ich diesem Code in Produktion vertrauen?“
Was getestet werden sollte, hängt immer vom Projekt ab. Doch wer sich auf Verhalten statt Implementierung konzentriert, schreibt Tests, die wirklich Fehler verhindern – und nicht nur einen Bericht verschönern.
Wenn Sie noch nie mit Coverage-Reports gearbeitet haben, empfehle ich diesen Beitrag: Making Use of Code Coverage.
Wie man gute Tests schreibt
Gute Tests zu schreiben ist eine Fähigkeit, die hervorragende Entwicklerinnen und Entwickler von guten unterscheidet. Hier einige praxisnahe Prinzipien:
Schnell schreiben und ausführen
Zuverlässig (nicht „flaky“) arbeiten
Leicht lesbar und verständlich schreiben
Sinnvoller Einsatz von Mocks
Ein klarer Gedanke pro Test
Mit derselben Sorgfalt arbeiten wie bei Produktionscode
Tests sollten schnell sein
Wir schreiben Tests, um Zeit zu sparen, Fehler früh zu erkennen und Refactorings sicher zu machen. Doch wenn Tests selbst langsam werden, verfehlen sie ihren Zweck.
Langsame Tests bremsen: Entwicklerinnen und Entwickler führen sie seltener lokal aus, und CI-Pipelines werden zu Produktivitätskillern. Wenn das Schreiben eines Tests länger dauert als der Code selbst, ist entweder der Testansatz oder der Code zu komplex.
Wenn Tests
schnell auszuführen sind, werden sie konsequent genutzt
schnell zu schreiben sind, werden sie überhaupt geschrieben
leicht zu verstehen sind, verbessert sich die Wartbarkeit
Die versteckte Kostenfalle unzuverlässiger Tests
Vertrauen schwindet: Zufällige Fehlschläge führen dazu, dass Tests ignoriert werden.
Zeit wird vergeudet: Die Analyse falscher Fehlermeldungen kostet Stunden.
Signal geht verloren: Echte Probleme werden durch Fehlalarme verdeckt.
Wenn ein instabiler Test nur mit übermässigem Aufwand reparierbar wäre, keine kritische Funktion prüft oder mehr Pflege kostet als er nützt – löschen Sie ihn.
Tests müssen verständlich sein
Ein häufiges Problem: Wir behandeln Tests als weniger wichtig als Produktionscode und vergessen dabei unsere eigenen Qualitätsansprüche.
DRY vs. Lesbarkeit
Tests sind in erster Linie Spezifikationen, erst danach Code. Während DRY für Produktivcode entscheidend ist, darf in Tests gezielte Wiederholung die Lesbarkeit erhöhen.

Gutes Beispiel: Klar und in sich abgeschlossen. Keine externen Hilfsfunktionen.
Keine externen Helper oder Setups
Konkrete Daten und Beträge erzählen die Geschichte
Die Business-Regel ist sofort erkennbar

Schlechtes Beispiel: Nutzt externe Helper, die nicht direkt sichtbar sind.
Testname ist unklar (wie spät? welche Gebühr?)
Wichtige Business-Logik (über 30 Tage) ist versteckt
Lesende müssen zwischen Dateien hin- und herspringen
Testlänge
Alle sind sich einig, dass eine Funktion mit 50 Zeilen kein gutes Zeichen ist, aber bei Tests scheint das oft akzeptabel zu sein.
Ein guter Test sollte so kurz sein, dass er vollständig auf den Bildschirm passt (typischerweise 5 bis 15 Zeilen). Wenn er länger ist, übernimmt er wahrscheinlich zu viele Aufgaben gleichzeitig.

Gutes Beispiel: Nur drei Zeilen lang und klar auf das zu testende Verhalten fokussiert.

Schlechtes Beispiel: Zu lang und prüft mehrere Dinge in einem Test – zum Beispiel das Hinzufügen und Entfernen von Artikeln sowie das Anwenden eines Rabatts.
Ein Konzept pro Test
Jeder Test sollte genau ein spezifisches Verhalten prüfen – nicht mehrere Szenarien gleichzeitig.

In den folgenden Beispielen konzentrieren wir uns in jedem Test auf eine einzige Prüfung. Im ersten Test geht es beispielsweise darum zu überprüfen, dass sich Artikel zum Warenkorb hinzufügen lassen und sich der Gesamtpreis dabei korrekt aktualisiert.
Die AAA-Teststruktur: Arrange, Act, Assert
Ein gut aufgebauter Test folgt dem AAA-Muster (Arrange–Act–Assert):
Arrange: Richten Sie den Testkontext ein. Bereiten Sie alle benötigten Objekte, Mocks und Daten vor. Wenn Sie beispielsweise eine Methode einer Klasse testen, müssen Sie diese Klasse zunächst instanziieren.
Act: Führen Sie die Aktion aus, die getestet werden soll. Zum Beispiel: Rufen Sie eine Methode mit bestimmten Parametern auf.
Assert: Überprüfen Sie, ob das Ergebnis der Aktion dem erwarteten Verhalten entspricht. Zum Beispiel: Das Ergebnis des Methodenaufrufs muss einen bestimmten Wert zurückgeben.
Nur das einbeziehen, was für den jeweiligen Test wirklich relevant ist. Die konkreten Daten in einem Test sollten nur dazu dienen, ihn von anderen Tests zu unterscheiden. Daten, die für das zu prüfende Verhalten keine Rolle spielen, sollten ausgeblendet oder vereinfacht werden. So weiss man: Wenn im Test konkrete Werte, Strings oder Zahlen auftauchen, sind sie wichtig und verdienen Aufmerksamkeit.

Gutes Beispiel: „Es wird nur ein User erzeugt, bei dem das Alter relevant ist, weil überprüft werden soll, ob die Funktion isAdult
bei einem Alter von 18 den Wert true
zurückgibt.“

Schlechtes Beispiel: „Wir wollen testen, dass die Funktion isAdult
bei einem Alter über 18 true
zurückgibt. Im Test werden jedoch zusätzliche Eigenschaften des User-Objekts definiert, die für diesen Test keine Bedeutung haben.“
Testbeschreibung
Wir investieren viel Zeit in die Benennung von Variablen – doch bei Tests wird die Bedeutung klarer Namen oft unterschätzt. Dabei ist es genauso wichtig, was Sie testen, wie wie Sie es benennen.
Ein Test kann als lebende Dokumentation Ihres Codes dienen: Er beschreibt das Systemverhalten und wird automatisch aktualisiert, sobald sich die Business-Logik ändert. Eine präzise Testbenennung hilft, Problem und Lösung besser zu verstehen – und ist daher eine Investition, die sich auszahlt.
Testnamen sollten klare Aussagen in der Sprache des Business über das Systemverhalten treffen. Wenn jemand allein durch das Lesen der Testnamen versteht, wie sich das System verhält, sind die Namen gut gewählt.
Der Inhalt des Tests stellt ein konkretes Beispiel für ein Szenario dar – eine Momentaufnahme des Systemverhaltens zu einem bestimmten Zeitpunkt mit bestimmten Werten. Daher sollte der Testname keine konkreten Daten enthalten, sondern die allgemeine Geschäftsregel, die der Test überprüft.

Gutes Beispiel (Shopping Cart):test cart with 2 items and SUMMER20 coupon should return 20% discount → Zeigt klar die Geschäftsregel: Rabatt wird korrekt angewendet.

Gutes Beispiel: applies 15% discount for premium users → Beschreibt die erwartete Business-Logik ohne unnötige Details.
Mocks sinnvoll einsetzen
Test-Doubles sind ein notwendiges Übel. Je weniger Sie mocken, desto stärker ähnelt Ihr Test der realen Umgebung. Zu viel Mocking reduziert die Aussagekraft der Tests – zu wenig Mocking kann sie dagegen verlangsamen.
Wie beim Entscheiden, was getestet werden soll, ist auch das Wissen, was gemockt werden muss, keine einfache Aufgabe. Zwei Dinge haben mir geholfen, bessere Entscheidungen zu treffen: die verschiedenen Arten von Mocks zu verstehen und typische Fehler zu vermeiden.
Arten von Mocks
Die genauen Bezeichnungen sind weniger wichtig als das Verständnis ihrer Unterschiede – dieses Wissen macht den entscheidenden Unterschied.
Dummy: Ein einfaches Platzhalterobjekt oder -wert, das als Parameter benötigt wird, aber keine Rolle in der eigentlichen Testlogik spielt. Verwenden Sie einen Dummy, wenn ein Objekt übergeben werden muss, dessen Verhalten für diesen Testfall jedoch irrelevant ist.
Stub: Liefert vordefinierte Antworten auf Methodenaufrufe. Wird eingesetzt, wenn die zu testende Komponente nicht nur von ihren Parametern, sondern auch von einer externen Datenquelle abhängt. Stubs ersetzen diese Quelle gezielt.
Mock & Spy: Überprüfen Interaktionen – zum Beispiel, ob eine Methode mit bestimmten Parametern aufgerufen wurde. Der Unterschied: Ein Spy delegiert an das reale Objekt und behält dessen Verhalten bei.
Fake: Eine vereinfachte, funktionsfähige Implementierung, die eine echte Abhängigkeit ersetzt – häufig, um Geschwindigkeit oder Einfachheit zu erreichen. Ein klassisches Beispiel ist eine In-Memory-Datenbank im Testkontext.
Häufige Fehler
Over-Mocking: Wenn jede Abhängigkeit gemockt wird, hängen Tests zu stark von Implementierungsdetails ab und brechen bei Refactorings leicht.
Überprüfung von Implementierungsdetails mit Mock & Spy: Tests sollten primär Ergebnisse prüfen, nicht interne Zwischenschritte.
Mocking fremder Komponenten: Das Mocken von Third-Party-Bibliotheken oder Framework-Code (z. B. axios, fetch) kann dazu führen, dass sich Mocks vom echten Verhalten entfernen – Integrationsfehler bleiben so unentdeckt.
Testing als Priorität
Tests dürfen niemals als weniger wichtig betrachtet werden als der eigentliche Code. Eine einfache, aber wirkungsvolle Regel im Team lautet: Eine Aufgabe ist erst abgeschlossen, wenn die Tests implementiert, grün und refaktoriert sind.
Das bedeutet: Test und Implementierung gehören untrennbar zusammen. Aussagen wie
„Wir haben jetzt keine Zeit für Tests, wir holen das später nach“ dürfen in einem professionellen Entwicklungsprozess keinen Platz haben.
Gerade im Zeitalter KI-gestützter Entwicklung wird Sorgfalt und Überprüfung wichtiger denn je.
Ariadna Gomez Ruiz
Robuste, benutzerfreundliche Interfaces mit sauberem Code und zuverlässigen Tests entwickeln – das ist mein Anspruch. Performance, Accessibility und wartbare Lösungen stehen für mich im Mittelpunkt.