Zum Inhalt springen
← Zurück zum Blog
Tech Insights

Fakes können Grenzen nicht nachbilden

Camsol · · 5 Min. Lesezeit

Die Suite lief zwanzig Minuten und blieb grün. 412 Tests, kein einziger übersprungen, die Coverage über der Schwelle, die sich irgendwann mal jemand ausgedacht hatte. Der Build ging durch, das Image landete in der Registry, der Container startete. Vier Minuten später stand der erste echte Durchlauf still: EACCES: permission denied, open '/data/exports/report.csv'.

Der Prozess lief als uid 1001. Das gemountete Verzeichnis gehörte root. Eine Zeile im Dockerfile, in fünf Minuten repariert.

Teuer war nicht der Fix. Teuer war, dass zwanzig Minuten grüner Tests diesen Fehler unmöglich hätten finden können.

Ein Test besitzt seine Umgebung

Schau dir an, was der Test tat, der das Schreiben dieser Datei abdeckte. Er legte über mkdtemp ein Temp-Verzeichnis an, schrieb hinein, las zurück, verglich, räumte auf. Er lief als der Nutzer, unter dem der Test-Runner lief. Das Verzeichnis, in das er schrieb, hatte er selbst eine Millisekunde vorher erzeugt.

Das ist kein schlampiger Test. Das ist genau das, was ein Unit-Test sein soll: hermetisch, wiederholbar, ohne geteilten Zustand, egal ob er auf deinem Laptop oder in der Pipeline läuft. Damit er das sein kann, muss er sich seine Umgebung selbst bauen - das Temp-Verzeichnis, den gefälschten HTTP-Client, den Container, der in Wirklichkeit ein Objekt mit den passenden Methoden ist.

Und daraus folgt eine Eigenschaft, die selten jemand ausspricht: Wer sich seine Umgebung selbst baut, kann mit ihr nicht in Konflikt geraten. Ein Verzeichnis, das der Test in derselben Sekunde angelegt hat, gehört ihm. Ein Netzwerk, das aus einem Mock besteht, hat keine Firewall. Ein Container, der ein Objekt ist, hat keinen Nutzer.

Die Konflikte, die dort nicht entstehen können

Es lohnt sich, das einmal konkret durchzugehen, weil die Liste immer dieselbe ist:

Ein uid-Mismatch braucht zwei Parteien mit unterschiedlichen Vorstellungen davon, wem etwas gehört - das Host-Verzeichnis von uid 1000 angelegt, der Container läuft als 1001. Im Test gibt es nur eine Partei.

Ein geteilter Mount braucht zwei Prozesse, die auf dasselbe Volume schreiben und sich gegenseitig die Datei unter den Händen wegziehen. Im Test schreibt genau einer, in sein eigenes Verzeichnis.

root gegen non-root braucht ein Image, das gehärtet anders startet als das Dev-Image, unter dem alles bequem funktioniert hat. Der Test-Runner ist immer derselbe Nutzer.

Ein Netzwerk ohne Route nach außen braucht eine Egress-Regel, einen Proxy, einen internen DNS, ein Zertifikat von einer CA, die deine Laufzeitumgebung nicht kennt. Der gefälschte HTTP-Client antwortet mit 200 und dem JSON, das du hineingeschrieben hast.

Dasselbe gilt für ein read-only Root-Filesystem, für ein Secret, das in Produktion als Datei gemountet statt als Umgebungsvariable gesetzt ist, für ein Memory-Limit, für eine Zeitzone. Es sind keine exotischen Fälle. Es sind die häufigsten Gründe, warum eine Software beim ersten echten Start umfällt - und sie haben alle dieselbe Form: ein Konflikt zwischen deinem Prozess und etwas, das schon vorher da war und dir nicht gehört.

Genau das ist die Sache, die ein Fake nicht kann. Ein Fake bildet eine Schnittstelle nach: dieselben Methoden, dieselben Rückgabewerte, dasselbe Verhalten bei Fehlern, wenn du dir Mühe gibst. Er bildet nicht nach, wovon diese Schnittstelle umgeben ist. Ein Fake ist ein Versprechen, das du dir selbst gegeben hast, und er antwortet so, wie du angenommen hast, dass die Welt antwortet.

Was ein grüner Test tatsächlich aussagt

Deshalb ist “die Tests sind grün” eine wahre Aussage und eine engere, als sie klingt. Sie heißt: Bei diesen Eingaben tut diese Funktion das Richtige. Meine Annahmen sind untereinander konsistent.

Sie heißt nicht: Diese Funktion wird jemals mit einem Verzeichnis aufgerufen, in das sie schreiben darf.

Das ist kein Argument gegen Tests. Wir schreiben sie in jedem Projekt, und wir schreiben sie zuerst, weil sie die einzige Prüfung sind, die schnell genug ist, um beim Schreiben zu helfen. Eine Suite, die in zwanzig Sekunden durchläuft, verändert, wie du arbeitest. Ein Integrationslauf, der zwölf Minuten braucht, tut das nicht. Tests prüfen Logik, und Logik ist der Teil, den du beim Refactoring kaputt machst.

Grenzen prüfen sie nicht. Dafür brauchst du einen Lauf, in dem nichts gefälscht ist.

Ein Lauf, der die Grenze anfasst

Das muss keine vollständige Staging-Umgebung sein. Für die meisten Projekte reicht ein einziger Durchlauf in der Pipeline, der drei Dinge ernst nimmt.

Er startet das Image, das du ausliefern würdest, nicht das Dev-Image mit den Debug-Tools und dem bequemen Entrypoint. Er mountet ein Verzeichnis, das jemand anderes angelegt hat als der Prozess, der hineinschreibt - und wenn das in Produktion root ist, dann eben root. Und er nimmt weg, was in Produktion nicht da ist: kein Egress, wenn es in Produktion keinen gibt, read-only, wenn das Root-Filesystem read-only ist.

Dann rufst du einmal den Weg auf, der in Produktion als erstes läuft. Nicht alle Wege, nicht mit allen Randfällen - dafür hast du die Unit-Tests. Einmal den Hauptweg, ganz durch, mit echtem Dateisystem und echtem Nutzer.

Der Fehler von oben wäre an dieser Stelle aufgetaucht. In der Pipeline, mit derselben Meldung, drei Minuten nach dem Commit statt vier Minuten nach dem Deployment.

Wer nur eins von beidem hat

Wer nur Unit-Tests hat, bekommt schnelles Feedback über Logik und ist blind für alles, was mit der Umgebung zu tun hat. Grün heißt dort: Meine Annahmen widersprechen sich nicht.

Wer nur Integrationsläufe hat, hat es umgekehrt. Der Lauf sieht die echte Welt, aber er ist langsam, er wird mit der Zeit flaky, und wenn er rot wird, sagt er dir nicht, welche von vierzig Komponenten schuld ist. Du debuggst dann rückwärts durch ein System statt vorwärts durch eine Funktion.

Zusammen ergeben sie etwas, das keiner von beiden allein leistet: Der Unit-Test lokalisiert, der Integrationslauf deckt auf. Der eine sagt dir, wo der Fehler sitzt, der andere überhaupt erst, dass es einen gibt.

Und der Unterschied, auf den es wirklich ankommt, ist nicht “weniger Bugs”. Es ist, welche Sorte Überraschung übrig bleibt. Mit beidem hast du Lücken, von denen du weißt - Dinge, die du bewusst nicht abgedeckt hast. Mit nur einem davon hast du Lücken, von denen du nichts weißt, und die Stelle, an der du sie kennenlernst, ist Produktion.

Haben Sie Projektbedarf?

Tobias

Gespräch vereinbaren →