Entwicklung & Technik8 Min. Lesezeit26. Juni 2026
Engineering-Handwerk · Tag 6: Testing, das sich lohnt
Warum die Testpyramide zählt: viele Unit-, wenige E2E-Tests, was sich zu testen lohnt und wie viel Testing wirklich genug ist.
Willkommen zu Tag 6 unserer Serie Engineering-Handwerk. Heute geht es um Testing – und zwar um die Frage, die viel wichtiger ist als „schreiben wir Tests?“: Welche Tests lohnen sich wirklich? Denn Tests sind nicht per se gut. Ein Test kann dich schneller machen oder dich jeden Tag ein bisschen ausbremsen. Er kann dir beim Refactoring den Rücken freihalten oder dich zwingen, bei jeder harmlosen Änderung zwölf Zusicherungen nachzuziehen, die eigentlich nichts prüfen. Der Unterschied liegt selten daran, ob getestet wird, sondern was und auf welcher Ebene. Genau darum dreht sich dieser Beitrag.
Wir sehen in Projekten regelmäßig zwei Extreme. Das eine Team hat gar keine Tests und traut sich an keinen zentralen Codepfad mehr heran, weil niemand weiß, was dabei kaputtgeht. Das andere Team hat 4.000 Tests, von denen die Hälfte nur bestätigt, dass ein Mock so aufgerufen wurde, wie man ihn eine Zeile vorher konfiguriert hat – und trotzdem gehen in Produktion echte Bugs durch. Beide Teams haben dasselbe Problem: Sie haben nie entschieden, wofür ihre Tests eigentlich da sind. Fangen wir dort an.
Wofür Tests wirklich da sind
Ein Test hat im Kern eine einzige Aufgabe: dir Vertrauen zu geben, dass eine bestimmte Aussage über dein System stimmt – und zwar auch morgen noch, wenn du oder ein Kollege den Code angefasst hat. Alles andere folgt daraus. Ein guter Test ist ein Sicherheitsnetz für Veränderung. Er sagt nicht „der Code ist richtig“, sondern „das Verhalten, das dem Nutzer wichtig ist, funktioniert noch“.
Dieser Unterschied ist der ganze Trick. Wenn du testest, wie eine Funktion intern etwas tut – welche private Methode sie aufruft, in welcher Reihenfolge sie ihre Helfer bemüht – dann verhindert dein Test genau das, wozu Tests eigentlich da sind: Refactoring. Du willst die Interna doch ändern können, ohne dass die Kundschaft es merkt. Ein Test, der an den Interna klebt, bricht bei jedem Umbau, obwohl das Verhalten identisch geblieben ist. Solche Tests sind negatives Kapital. Sie kosten bei jeder Änderung Zeit und fangen nie einen echten Fehler.
Deshalb ist die erste Regel beim Testing nicht technisch, sondern eine Haltungsfrage: Teste beobachtbares Verhalten, nicht Implementierung. Frag dich vor jedem Test: „Wenn ich diese Zusicherung breche, ist dann etwas kaputt, das jemandem wehtut?“ Wenn die Antwort nein ist, lösch den Test oder schreib ihn nie.
Die Testpyramide: viele unten, wenige oben
Die Testpyramide ist ein altes Bild, aber es hält, weil es über Kosten spricht, nicht über Ideologie. Sie sortiert Tests nach Ebene – von schnell und eng unten bis langsam und breit oben.
Unit-Tests: die breite Basis
Unten liegen die Unit-Tests. Sie prüfen eine einzelne Einheit – eine Funktion, eine Klasse, ein kleines Modul – isoliert und in Millisekunden. Davon willst du viele. Sie sind billig zu schreiben, laufen in Sekundenbruchteilen und zeigen dir bei einem Fehlschlag sofort die genaue Stelle. Ideal sind sie für alles, was Logik ist: Berechnungen, Zustandsübergänge, Validierung, Grenzfälle, das Verhalten bei komischen Eingaben. Ein Preis-Rundungsproblem, eine Rabattlogik, ein Parser für ein krummes Datumsformat – das gehört in schnelle Unit-Tests, wo du zwanzig Varianten in einer Tabelle durchspielst, ohne dass jemand eine Datenbank hochfahren muss.
Integrationstests: die mittlere Schicht
In der Mitte sitzen Integrationstests. Sie prüfen, ob mehrere Teile zusammen richtig funktionieren – typischerweise dein Code plus eine echte Abhängigkeit: die Datenbank, eine Queue, ein HTTP-Endpunkt. Hier fängst du die Fehlerklasse, die Unit-Tests systematisch verpassen: die falsche SQL-Query, das vergessene Feld im Mapping, die Migration, die nicht durchläuft, der Serialisierungsfehler an der Grenze zwischen zwei Systemen. Davon brauchst du deutlich weniger als Unit-Tests, aber die, die du hast, sind wertvoll, weil sie genau dort prüfen, wo die realen Bugs entstehen: an den Nähten.
Wichtig: Integrationstests gegen ein echtes System, nicht gegen ein nachgebautes. Eine gemockte Datenbank testet dein Verständnis der Datenbank, nicht die Datenbank. Wir fahren dafür bewusst eine echte Postgres-Instanz in einem Container hoch, statt SQLite als billigen Ersatz zu nehmen – sonst prüfst du am Ende Verhalten, das in Produktion so gar nicht existiert.
End-to-End-Tests: die schmale Spitze
Ganz oben sitzen die E2E-Tests. Sie fahren das ganze System hoch und spielen einen echten Nutzerpfad durch: einloggen, etwas anlegen, absenden, Ergebnis prüfen. Sie sind das ehrlichste Signal, das du bekommst – und das teuerste. Sie sind langsam, brauchen viel Infrastruktur und neigen zur Fragilität. Deshalb gehören hier nur die wenigen Flüsse hin, deren Ausfall dich wirklich schmerzt: der Login, der Checkout, der eine Kernworkflow, an dem dein Geschäft hängt. Fünf bis zwölf gute E2E-Tests, die zuverlässig grün sind, sind mehr wert als hundert, die niemand mehr ernst nimmt.
Die Form der Pyramide folgt direkt aus den Kosten: Je höher du gehst, desto langsamer, teurer und wackeliger wird jeder Test – also willst du oben wenige und unten viele. Wer die Pyramide auf den Kopf stellt (viele E2E, kaum Unit-Tests), landet bei einer Suite, die zwanzig Minuten läuft, sporadisch rot wird und trotzdem Lücken hat.
Was sich zu testen lohnt – und was nicht
Nicht alles verdient einen Test. Das offen zu sagen, ist unpopulär, aber ehrlich. Deine Testzeit ist endlich, und jeder Test, der wenig fängt, kostet trotzdem Wartung. Also priorisiere nach Risiko und Änderungshäufigkeit.
Lohnt sich: Logik mit vielen Verzweigungen und Grenzfällen. Alles, wo Geld, Rechte oder Datenintegrität im Spiel sind. Code, den viele anfassen und der sich oft ändert – dort verhindert das Netz teure Regressionen. Und jeder Bug, den du gerade fixt: Schreib zuerst den Test, der ihn reproduziert, dann den Fix. So kann derselbe Fehler nie zweimal zurückkommen.
Lohnt sich meist nicht: triviale Getter und Setter, reine Konfigurationsobjekte, dünne Weiterreich-Schichten ohne eigene Logik, generierter Code. Und: Frameworks testet man nicht. Du musst nicht prüfen, ob deine ORM-Bibliothek speichern kann – das ist deren Job. Teste deinen Anteil, nicht den der Bibliothek.
Ein nützlicher Kompass ist die Frage nach der Halbwertszeit. Testet dieser Test etwas, das in sechs Monaten noch genauso gelten soll? Dann ist er ein Investment. Oder hält er nur den heutigen Implementierungszufall fest? Dann ist er Ballast.
Die Kosten schlechter Tests
Schlechte Tests sind schlimmer als keine Tests, weil sie so aussehen, als würden sie helfen. Zwei Kostenarten fallen besonders ins Gewicht.
Langsame Suites
Eine Testsuite, die zwanzig Minuten braucht, wird nicht mehr lokal ausgeführt. Sie wandert in die CI, und dort wartet niemand gern auf sie. Das Feedback, das dir ein Test geben soll – sofort zu wissen, dass du etwas kaputtgemacht hast –, kommt dann eine halbe Stunde später, wenn du längst woanders bist. Schnelligkeit ist keine Kosmetik, sie ist der Grund, warum Unit-Tests wirken. Verlagere Aufwand nach unten in der Pyramide, halte die Unit-Ebene unter ein paar Sekunden für das ganze Paket, und die Suite bleibt ein Werkzeug statt einer Pflichtübung.
Flaky Tests
Noch teurer sind wackelige Tests – solche, die mal grün und mal rot sind, ohne dass sich der Code geändert hat. Sie vergiften das Vertrauen in die gesamte Suite. Sobald ein Team gelernt hat, rote Tests „einfach nochmal laufen zu lassen“, ist die Suite als Signal tot. Dann rutscht irgendwann ein echter Fehler durch, weil ihn alle für das übliche Flackern hielten. Flüchtigkeit kommt fast immer aus denselben Quellen: echte Zeit (statt kontrollierbarer Uhr), Zufälligkeit, Nebenläufigkeit ohne saubere Synchronisation, geteilter Zustand zwischen Tests, Abhängigkeit von der Ausführungsreihenfolge. Behandle einen flaky Test wie einen Produktionsbug, nicht wie eine Lästigkeit: Finde die Ursache oder lösch den Test. Ein zuverlässig aussagekräftiger Test ist Gold; ein unzuverlässiger ist Sand im Getriebe.
Testbarkeit ist ein Design-Signal
Hier kommt der Teil, den viele übersehen. Wenn sich etwas schwer testen lässt, ist das selten ein Testproblem – es ist meistens ein Designproblem. Eine Funktion, die nur mit fünf Mocks, einer echten Uhr und einem globalen Zustand zu prüfen ist, sagt dir etwas: Sie tut zu viel, oder ihre Abhängigkeiten sind fest verdrahtet statt hereingereicht.
Das ist eine gute Nachricht, weil Testbarkeit dir kostenlos Feedback zur Architektur gibt. Wenn du die reine Logik von den Seiteneffekten trennst – Berechnung hier, Datenbankzugriff dort –, wird beides einzeln testbar, und ganz nebenbei auch verständlicher und leichter wiederzuverwenden. Wir nutzen den Schmerz beim Testen deshalb bewusst als Kompass: Wenn ein Test unangenehm zu schreiben ist, fragen wir zuerst, ob der Code umgestellt gehört, statt den Test mit Mock-Gymnastik zu erzwingen. Oft löst sich beides zusammen – besseres Design und einfacherer Test.
Praxisbeispiel: der Rabatt-Rechner, der leise falsch lag
Ein konkreter Fall aus einem Shop-Backend. Es gab eine Funktion, die den Endpreis eines Warenkorbs berechnete: Mengenrabatt, Gutscheincode, Mindestbestellwert, Rundung auf zwei Nachkommastellen. Getestet war das Ganze über einen einzigen E2E-Test, der einen Beispielwarenkorb durch den kompletten Checkout schickte und am Ende prüfte, dass irgendein Preis herauskam. Grün. Alle zufrieden.
Dann meldete ein Kunde, dass ein 10-Prozent-Gutschein bei einem bestimmten Betrag einen Cent zu viel abzog. Der E2E-Test hatte das nie gesehen, weil er nur einen einzigen Betrag kannte – und zwar zufällig einen, bei dem die Rundung aufging. Der Fehler lag darin, dass zuerst gerundet und dann der Rabatt angewendet wurde, statt umgekehrt. Ein klassischer Grenzfall, den ein breiter Test strukturell nicht findet.
Wir haben es umgedreht. Die Preislogik wurde aus dem Checkout-Fluss herausgezogen in eine reine Funktion ohne Datenbank und ohne Zeitbezug. Darum kamen rund fünfzehn Unit-Tests in einer Tabelle: verschiedene Beträge, Rabatt vor und nach Rundung, Gutschein unter und über Mindestbestellwert, der Fall mit dem einen Cent explizit als Regressionstest. Der Bug war in Minuten reproduziert und gefixt. Der eine E2E-Test blieb erhalten – aber nur noch als grober Wächter, dass der Checkout überhaupt durchläuft, nicht als Ersatz für die feine Logikprüfung. Genau so soll die Pyramide arbeiten: viele billige Tests fangen die Details, wenige teure sichern den Gesamtpfad.
So gehst du vor
Wenn du deine Teststrategie ordnen willst, hilft ein nüchterner, schrittweiser Weg statt einer großen Test-Offensive.
- Beginne bei den Schmerzstellen, nicht bei der Coverage-Zahl. Nimm die drei Codepfade, deren Ausfall dich am meisten kosten würde, und sichere zuerst die ab.
- Schreib für jeden Bug zuerst den reproduzierenden Test. So wächst die Suite genau dort, wo real etwas schiefging – das ist die ehrlichste Priorisierung, die es gibt.
- Verschiebe Aufwand nach unten. Wann immer ein E2E-Test etwas prüft, das auch ein Unit-Test könnte, zieh die Prüfung nach unten. Schnell und eng schlägt langsam und breit.
- Trenne Logik von Seiteneffekten. Reine Berechnung ohne Datenbank, Uhr oder Netzwerk lässt sich trivial testen. Wenn das schwerfällt, ist das ein Hinweis auf den nächsten Refactoring-Schritt.
- Miss die Laufzeit und hüte sie. Halte die schnelle Suite wirklich schnell, damit sie lokal läuft. Ein Test, den niemand ausführt, schützt nichts.
- Dulde keinen flaky Test. Repariere ihn oder entferne ihn am selben Tag. Ein einziger tolerierter Wackler senkt das Vertrauen in alle anderen.
- Frag bei jedem Test: fängt er echtes Verhalten? Wenn er nur die Implementierung nachzeichnet, gib ihn nicht ins Repository.
Und die Frage, die am häufigsten kommt: Wie viel Testing ist genug? Es gibt keine magische Prozentzahl. Hundert Prozent Coverage sagen nur, dass jede Zeile einmal ausgeführt wurde – nicht, dass sie richtig ist. Ein besserer Maßstab ist Ruhe: Genug getestet ist, wenn du eine größere Änderung machen kannst und der grünen Suite glaubst, dass nichts Wichtiges kaputt ist. Wenn du nach jedem Refactoring trotzdem alles per Hand nachklickst, fehlen dir Tests an den richtigen Stellen. Wenn du bei einer harmlosen Änderung zwanzig Tests reparieren musst, hast du zu viele an den falschen. Ziel ist nicht Maximum, sondern das Netz, das dich schnell lässt.
Fazit
Testing lohnt sich nicht, weil man „Tests haben soll“, sondern weil gute Tests dich schneller und mutiger machen. Die Testpyramide gibt dir die Kostendisziplin dafür: viele schnelle Unit-Tests für die Logik, weniger Integrationstests an den Nähten, wenige E2E-Tests für die kritischen Pfade. Teste Verhalten statt Implementierung, halte die Suite schnell und ehrlich, dulde keine wackeligen Tests, und höre auf das Design-Signal, wenn etwas schwer testbar ist. Dann wird deine Suite genau das, wofür sie gedacht ist: ein Sicherheitsnetz, das dich beim Umbauen trägt, statt dich festzuhalten.
Alle bisherigen Folgen findest du in der Serien-Übersicht. Wenn du willst, dass Testing in deinem Projekt vom Lästigkeitsposten zum echten Sicherheitsnetz wird – von der richtigen Pyramide bis zu wartbaren, schnellen Suites –, unterstützen wir dich in der Softwareentwicklung gerne dabei. Erzähl uns über das Kontaktformular, woran dein Team gerade hängt, und wir schauen es uns gemeinsam an.