Inhalt
Warum testen wir End-to-End gegen eine echte Datenbank?
Weil ein Mock die Annahme bestätigt, die man beim Schreiben hatte, und eine echte Datenbank ihr widerspricht. Die Fehler, die in verteilten Systemen teuer werden, sitzen genau dort, wo der Mock nichts weiß: in Transaktionsgrenzen, Isolationsstufen, der Reihenfolge von Migrationen und im Zeitzonenverhalten von Zeitstempeln. Ein Test gegen ein In-Memory-Substitut misst das Substitut, nicht das System.
In EcoPulse laufen die End-to-End-Tests der REST-Services deshalb gegen eine real hochgefahrene API mit echter Datenbank — dieselbe PostgreSQL-Version wie in Produktion, dieselben Migrationen, dieselben Constraints. Das ist kein Perfektionismus, sondern die Konsequenz aus Befunden, die anders nicht sichtbar geworden wären.
Was genau startet Testcontainers — und was nicht?
Testcontainers fährt vor dem Testlauf echte Container hoch — PostgreSQL mit TimescaleDB, Redis, einen Kafka-Broker — und gibt dem Test die tatsächlichen Verbindungsdaten. Nach dem Lauf verschwinden sie wieder. Was es nicht kann: Geräte. Ein Wechselrichter, eine Wallbox oder eine SPS steht in keiner CI. Diese Grenze bestimmt den Zuschnitt der gesamten Testarchitektur.
Daraus folgt eine Zweiteilung, die sich bei uns bewährt hat:
- Infrastruktur wird echt hochgefahren. Datenbank, Cache, Broker — alles, was ein Container sein kann, ist ein Container.
- Fremdgeräte werden über aufgezeichnete Telemetrie ersetzt, nicht über handgeschriebene Mocks. Der Unterschied ist erheblich: Eine Aufzeichnung bringt die Ausreißer, Lücken und Doppelmeldungen mit, die im Feld tatsächlich auftreten. Ein handgeschriebener Mock bringt das mit, woran der Autor gedacht hat.
Was kostet das in der Pipeline-Laufzeit?
Ehrlich: Zeit. Ein Container braucht Sekunden zum Start, und wer das pro Testklasse macht, hat schnell eine Pipeline, die niemand mehr abwartet. Eine Pipeline, die niemand abwartet, ist als Qualitätsgate wertlos, weil sie umgangen wird.
Die Kosten sind real, aber steuerbar. Der Fehler ist nicht, echte Abhängigkeiten zu starten. Der Fehler ist, sie zu oft zu starten.
Wie hält man die Laufzeit beherrschbar?
Über vier Maßnahmen, die zusammen den Unterschied zwischen zehn Minuten und einer Stunde ausmachen:
Container pro Testsuite wiederverwenden, nicht pro Testmethode. Zwischen den Tests wird der Zustand zurückgesetzt, nicht der Container neu gestartet. Das ist der größte Einzelhebel.
Testebenen trennen. Unit-Tests laufen bei jedem Commit in Sekunden; die containergestützten Integrations- und E2E-Tests laufen im Merge-Gate. Wer beides in denselben Lauf packt, bestraft jeden Commit mit der Laufzeit des teuersten Tests.
Ehrlich parallelisieren. Tests laufen parallel, wenn sie wirklich unabhängig sind — und das gehört geprüft, nicht angenommen. Zwei Suiten, die sich dieselbe Datenbank teilen, sind nicht unabhängig, auch wenn sie beim ersten Lauf so aussehen.
Schwere Jobs serialisieren. In einem unserer Projekte haben vier parallel laufende Feature-Pipelines einen 25-Minuten-Lauf auf über eine Stunde gedehnt; zwei Jobs starben im Zeitlimit des Runners. Vier serielle Läufe, die durchlaufen, sind billiger als vier parallele, die alle scheitern. In GitLab CI erledigt das eine resource_group.
Wie testet man Autorisierungspfade?
Indem man sie als eigene Testfälle führt, nicht als Nebenbedingung. Der übliche Fehler ist, jeden Test als berechtigter Nutzer laufen zu lassen und anzunehmen, die Rechteprüfung greife schon. Sie greift genau so lange, bis jemand eine Route ergänzt und die Prüfung vergisst — und dann meldet keine Testsuite etwas.
Wir prüfen deshalb je Endpunkt mindestens drei Fälle: berechtigt, unberechtigt, unauthentifiziert. In mandantenfähigen Systemen kommt ein vierter dazu, der wichtigste: berechtigt, aber für einen anderen Mandanten. Genau dieser Fall trennt eine funktionierende Mandantentrennung von einer, die nur so aussieht — und er lässt sich nur gegen eine echte Datenbank prüfen, weil er von Daten abhängt, die dort liegen.
Wie testet man Fehlerpfade?
Indem man den Fehler tatsächlich herstellt statt ihn zu simulieren. Mit echten Containern geht das: Die Datenbank wird während des Laufs gestoppt, der Broker verliert die Verbindung, eine Migration läuft gegen einen unerwarteten Bestand. Das sind die Zustände, in denen sich entscheidet, ob ein System sauber ausfällt oder Daten verliert.
Zwei Klassen lohnen besonders:
- Wiederanlauf. Was passiert, wenn ein Service nach einem Ausfall neu startet und der Ereignisstrom weitergelaufen ist? Bei einer ereignisgetriebenen Plattform ist das kein Randfall, sondern Normalbetrieb.
- Teilausfall. Der Cache ist weg, die Datenbank steht. Antwortet der Service dann langsamer — oder falsch?
Ein Statuscode allein ist dabei keine Aussage. In einem anderen Projekt lieferten vier Backend-Routen still HTTP 200 mit dem Inhalt der Single-Page-App statt der API-Antwort; der Client prüfte IsSuccessStatusCode, das bei 200 wahr ist, und meldete zufrieden weiter. Fehlerpfad-Tests prüfen bei uns seitdem Statuscode und Content-Type.
Was ist mit Streaming- und Zeitreihendaten?
Das ist der Teil, den Standardtestwerkzeuge nicht abdecken. Eine Zeitreihenauswertung ist erst dann geprüft, wenn sie mit unregelmäßigen Abständen, Lücken, doppelten Zeitstempeln und rückwirkend eintreffenden Messwerten umgeht — und all das liefert kein Mock, weil niemand daran denkt.
Wir fahren dafür TimescaleDB als Container hoch und spielen aufgezeichnete Telemetrie ein. Geprüft wird nicht nur das Ergebnis, sondern seine Stabilität: Dieselbe Auswertung über denselben Zeitraum muss denselben Wert liefern, unabhängig davon, in welcher Reihenfolge die Messwerte eingetroffen sind.
Woher kommen die Testdaten?
Aus synthetischen Beständen, die aus echten Aufzeichnungen abgeleitet sind. In Projekten der öffentlichen Verwaltung ist das Pflicht — Echtdaten scheiden aus. In Industrieprojekten ist es die bessere Wahl, weil ein synthetischer Bestand reproduzierbar ist und ein Produktionsabzug nicht.
Wichtig ist, die Eigenheiten mitzunehmen statt sie wegzuglätten: Ein Testbestand ohne Ausreißer prüft ein System, das es nicht gibt.
Wann ist Testcontainers das falsche Werkzeug?
In vier Fällen greifen wir zu etwas anderem:
- Wenn die Abhängigkeit kein Container sein kann. Eine SPS, ein Wechselrichter, ein Telefonendgerät. Für Phonalisa betreiben wir stattdessen dedizierte Testumgebungen für Signalisierung und Medienpfad — echte Hardware, weil sich Sprachqualität nicht containerisieren lässt.
- Wenn die Fachlogik nur über die Oberfläche erreichbar ist. Dann führt der Weg über GUI-Automatisierung: Squish bei Qt-Anwendungen, Selenium bei Weboberflächen. Beides ist teurer im Unterhalt und kommt deshalb zuletzt.
- Wenn ein Unit-Test reicht. Eine reine Berechnung gegen eine Datenbank zu prüfen, verlängert die Pipeline ohne Erkenntnisgewinn.
- Wenn die Umgebung keine Container zulässt. In manchen regulierten Umgebungen sind virtuelle Maschinen über Proxmox oder VMware der vorgeschriebene Weg.
Wie fängt man an, wenn nichts davon existiert?
Nicht mit der breitesten Ebene, sondern mit der teuersten Fehlerklasse. In der Praxis heißt das fast immer: ein Endpunkt, der Daten schreibt, vollständig gegen echte Infrastruktur geprüft — Erfolgsfall, unberechtigt, unauthentifiziert, fremder Mandant, und ein Fehlerpfad. Das sind fünf Tests, die an einem Tag stehen und ab dem ersten Lauf etwas absichern.
Danach wächst die Suite entlang der Änderungshäufigkeit, nicht entlang der Ordnerstruktur. Wo im letzten halben Jahr am meisten committet wurde, sitzt das Risiko — und dort zahlt sich Abdeckung zuerst aus. Der umgekehrte Weg, erst alles flächig zu erfassen, produziert eine lange Laufzeit ohne Aussage über die kritischen Pfade.
Ein Punkt gehört von Anfang an geklärt, weil er sich später kaum nachrüsten lässt: Die Suite muss im Merge-Gate laufen, nicht daneben. Eine Testsuite, die niemanden aufhält, wird bei der ersten roten Ampel abgeschaltet statt repariert.
Woran erkennt man, dass ein Test wirklich absichert?
Daran, dass er fehlschlägt, sobald man die Absicherung zurücknimmt. „Test hinzugefügt" ist keine Aussage über Qualität. Der Nachweis lautet: Mutation eingebaut, Test rot, Mutation zurückgenommen, Test grün. Erst dann ist belegt, dass der Test prüft, was sein Name behauptet.
Diese Prüfung ist unbequem und findet erstaunlich viel — auch an Tests, die seit Monaten grün waren. Ausführlich im Feldbericht Wie wir mit KI-Agenten entwickeln, wo derselbe Nachweis für agentenerzeugte Regressionstests verpflichtend ist.
Was wir daraus als Regel gemacht haben
Tests gegen echte Infrastruktur sind Pflicht im Merge-Gate. Was gegen Mocks läuft, ist ein Unit-Test und darf sich nicht E2E nennen. Die Unterscheidung klingt pedantisch, entscheidet aber darüber, wie viel Vertrauen eine grüne Pipeline verdient — und Vertrauen in die Pipeline ist die Voraussetzung dafür, dass sie überhaupt als Gate akzeptiert wird.
Wie wir das in bestehende Testlandschaften einführen, steht unter Qualitätssicherung & Testautomatisierung. Ob der Aufbau intern oder extern besser aufgehoben ist, behandelt Testautomatisierung intern aufbauen oder extern vergeben?