Teil B — Wie man arbeitet. Zurück zur Übersicht.
Lernziel
Nach diesem Kapitel kennst du die Testpyramide und ihre Umkehrung als Anti-Pattern, kannst Shift-Left von Shift-Right unterscheiden und weißt, warum Testabdeckung eine irreführende Kennzahl ist. Du hast automatisierte Tests für den KFZ-Rechner geschrieben und kennst die datenschutzrechtlichen Grenzen bei Testdaten.
Es klingt widersprüchlich: Tests kosten Zeit, sollen aber schneller machen. Der Zusammenhang wird klar, wenn man fragt, was ohne sie passiert.
Ohne verlässliche Tests ist jede Auslieferung ein Risiko. Wer Risiken scheut, liefert seltener aus. Wer seltener ausliefert, packt mehr Änderungen in jede Auslieferung. Je mehr Änderungen, desto größer das Risiko. Ein sich selbst verstärkender Kreislauf, an dessen Ende das halbjährliche Release mit vierzehn Beteiligten und einem Wochenende Bereitschaft steht.
Automatisierte Tests durchbrechen ihn. Sie sind nicht Selbstzweck, sondern die Erlaubnis, schnell zu sein.
Die entscheidende Frage lautet nicht „Haben wir Tests?„, sondern:
Trauen wir uns, am Freitagnachmittag auszuliefern?
Wer diese Frage mit Ja beantworten kann, hat gute Tests. Wer sie mit Nein beantwortet, hat unabhängig von jeder Abdeckungszahl ein Testproblem. Das ist übrigens auch ein brauchbarer Reifegradindikator für Bewerbungsgespräche — in beide Richtungen.
Das Modell geht auf Mike Cohn zurück und ordnet Tests nach Umfang, Geschwindigkeit und Kosten.
╱╲
╱ ╲ E2E-Tests
╱ ╲ wenige, langsam, teuer, fragil
╱──────╲ Minuten
╱ ╲
╱ ╲ Integrationstests
╱ ╲ einige, mittelschnell
╱──────────────╲ Sekunden
╱ ╲
╱ ╲ Unit-Tests
╱ ╲viele, blitzschnell, billig
╱______________________╲Millisekunden
| Ebene | Prüft | Anteil | Laufzeit |
|---|---|---|---|
| Unit | eine Funktion isoliert | 70 % | Millisekunden |
| Integration | Zusammenspiel mehrerer Teile, echte Datenbank | 20 % | Sekunden |
| End-to-End | den gesamten Weg wie ein Anwender | 10 % | Minuten |
Die Logik dahinter ist ökonomisch: Ein Unit-Test findet einen Fehler in Millisekunden und zeigt exakt die betroffene Zeile. Ein E2E-Test findet denselben Fehler nach vier Minuten und meldet lediglich, dass irgendetwas nicht stimmt.
╱────────────────────╲ manuelle Tests
╱ ╲ "einmal durchklicken"
╱────────────────────────╲
╲ ╱ E2E-Tests
╲ ╱ viele, langsam, fragil
╲──────────────────╱
╲ ╱ Integrationstests
╲──────────────╱ wenige
╲ ╱
╲__________╱ Unit-Tests
fast keine
Die umgedrehte Pyramide — der Ice Cream Cone — entsteht nie durch eine Entscheidung, sondern durch Unterlassung: Man testet am Ende über die Oberfläche, weil es dort ohne Vorarbeit möglich ist. Das Ergebnis ist eine Testsuite, die zwanzig Minuten braucht, sporadisch grundlos fehlschlägt und nach jeder Layout-Änderung repariert werden muss.
Die Abdeckung misst, welcher Anteil des Codes bei einem Testlauf ausgeführt wurde. Sie misst nicht, ob dabei etwas Sinnvolles geprüft wurde.
# Dieser Test erzeugt 100 % Abdeckung und prüft nichts def test_praemie(): berechne_praemie(5, 3) # keine Zusicherung, kein assert
Nützlich ist die Abdeckung, um ungetestete Bereiche zu finden — eine Datei mit 0 Prozent ist ein echtes Signal. Schädlich wird sie als Zielvorgabe: Sobald 80 Prozent verlangt sind, entstehen Tests, die Abdeckung erzeugen statt Fehler zu finden. Das ist ein Lehrbuchbeispiel für Goodharts Gesetz — wird eine Kennzahl zum Ziel, verliert sie ihre Aussagekraft.
Sinnvoller als eine Abdeckungsquote ist die Frage: Welche Fehler hat unsere Testsuite in den letzten sechs Monaten tatsächlich verhindert? Und die Gegenprobe: Welche Störungen sind trotz grüner Tests in Produktion aufgetreten? Jede solche Störung ist eine Einladung, genau dafür einen Test nachzuziehen.
Shift-Left bedeutet, Qualitätssicherung so früh wie möglich stattfinden zu lassen — im Zeitstrahl also nach links.
Klassisch:
Anforderung ── Entwurf ── Coding ──────────────── TEST ── Release
▲
alles am Ende
Shift-Left:
Anforderung ── Entwurf ── Coding ── Release
▲ ▲ ▲ ▲
Test Test Test Test
(Kriterien (Review) (Unit, CI) (Canary)
prüfbar?)
Eine verbreitete Fehldarstellung: Shift-Left bedeutet nicht, dass das Wasserfallmodell abgeschafft wird oder dass am Ende nicht mehr getestet wird. Es bedeutet, dass Qualitätssicherung an jedem Schritt stattfindet statt nur an einem.
Auch in einem Wasserfallprojekt kann man Anforderungen auf Prüfbarkeit abklopfen, Entwürfe im Review hinterfragen und Unit-Tests schreiben. Shift-Left ist eine Frage der Verteilung, nicht des Vorgehensmodells.
Konkrete Maßnahmen, von links nach rechts:
| Phase | Qualitätsmaßnahme |
|---|---|
| Anforderung | Akzeptanzkriterien formulieren: „Woran erkennen wir, dass es funktioniert?“ |
| Entwurf | Bedrohungsmodellierung, Prüfung auf Nachvollziehbarkeit |
| Coding | Unit-Tests, Linting, Formatierung, Pre-Commit-Hooks |
| Commit | automatischer Testlauf, Secret-Scanning |
| Build | Abhängigkeiten auf Schwachstellen prüfen |
| Vor dem Ausrollen | Integrations- und E2E-Tests, Lasttest |
Das Gegenstück wird selten mitgelehrt, ist aber ebenso wichtig: Manche Eigenschaften lassen sich nur in Produktion feststellen. Echte Last, echte Daten, echtes Nutzerverhalten, echte Netzwerke.
| Verfahren | Was es leistet |
|---|---|
| Canary Release | Neue Version zunächst für 5 % der Anfragen, Fehlerrate beobachten |
| Synthetisches Monitoring | Ein Roboter durchläuft alle fünf Minuten den Anmeldevorgang |
| Feature Flags | Funktion in Produktion vorhanden, aber abgeschaltet — gezielt zuschaltbar |
| Chaos Engineering | Kontrolliert Störungen erzeugen, um Annahmen zu prüfen |
| A/B-Test | Zwei Varianten parallel, Entscheidung anhand von Daten |
Chaos Engineering klingt nach mutwilliger Zerstörung, ist aber das genaue Gegenteil: Man prüft kontrolliert und zu einer Zeit, in der alle Beteiligten wach und ansprechbar sind, ob die Annahmen über die Ausfallsicherheit zutreffen. Die Alternative besteht darin, dieselbe Erkenntnis unkontrolliert um drei Uhr nachts zu gewinnen.
Ein Abschnitt, der in Versicherungs- und Bankumgebungen über Wohl und Wehe entscheidet.
Produktivdaten gehören nicht in Testsysteme. Ein Abzug der Vertragsdatenbank auf das Testsystem ist eine Verarbeitung personenbezogener Daten zu einem anderen Zweck als dem der Erhebung — sie braucht eine eigene Rechtsgrundlage, die in aller Regel nicht vorliegt.
Verschärfend kommt hinzu: Testsysteme haben schwächere Zugriffskontrollen, mehr Berechtigte, seltener eingespielte Patches und selten eine Protokollierung, die einer Prüfung standhält. Ein Datenabfluss aus dem Testsystem ist genauso meldepflichtig wie einer aus der Produktion.
Die sauberen Alternativen:
| Verfahren | Beschreibung | Eignung |
|---|---|---|
| Synthetische Daten | vollständig erfunden, per Generator erzeugt | erste Wahl |
| Anonymisierung | Personenbezug unwiderruflich entfernt | gut, wenn wirklich unwiderruflich |
| Pseudonymisierung | Bezug über Schlüssel wiederherstellbar | weiterhin personenbezogene Daten |
| Maskierung | Felder überschrieben, Struktur erhalten | brauchbar bei sorgfältiger Umsetzung |
# Synthetische Testdaten mit faker from faker import Faker fake = Faker("de_DE") testkunden = [ { "name": fake.name(), "plz": fake.postcode(), "sf_klasse": fake.random_int(0, 35), "fahrzeugalter": fake.random_int(0, 25), } for _ in range(1000) ]
Ein Hinweis zur Anonymisierung: Sie ist schwerer, als sie aussieht. Eine Kombination aus Postleitzahl, Geburtsdatum und Geschlecht identifiziert einen erheblichen Teil der Bevölkerung eindeutig, auch ohne Namen. Wer anonymisiert, sollte das Ergebnis prüfen lassen, nicht nur die Namensspalte überschreiben.
cd kfz-rechner python3 -m venv .venv && source .venv/bin/activate pip install pytest pytest-cov
"""Tests für die Prämienberechnung.""" from decimal import Decimal import pytest from src.praemie import berechne_praemie def test_grundfall(): """SF-Klasse 0 ohne Altersnachlass ergibt den vollen Grundbeitrag.""" assert berechne_praemie(0, 3) == Decimal("480.00") def test_sf_rabatt(): """SF-Klasse 5 gewährt 40 Prozent Nachlass.""" assert berechne_praemie(5, 3) == Decimal("288.00") def test_altersnachlass(): """Fahrzeuge über zehn Jahre erhalten zusätzlich zehn Prozent.""" assert berechne_praemie(5, 12) == Decimal("259.20") def test_unbekannte_sf_klasse(): """Nicht hinterlegte SF-Klassen fallen auf den Standardfaktor zurück.""" assert berechne_praemie(7, 3) == Decimal("240.00") def test_negative_sf_klasse_wirft_fehler(): """Negative SF-Klassen sind fachlich unmöglich.""" with pytest.raises(ValueError, match="negativ"): berechne_praemie(-1, 3) @pytest.mark.parametrize( "sf,alter,erwartet", [ (0, 0, "480.00"), (1, 0, "408.00"), (10, 0, "192.00"), (10, 15, "172.80"), ], ) def test_verschiedene_kombinationen(sf, alter, erwartet): """Prüft mehrere Kombinationen in einem Durchlauf.""" assert berechne_praemie(sf, alter) == Decimal(erwartet) def test_ergebnis_hat_zwei_nachkommastellen(): """Beträge werden auf Cent gerundet.""" ergebnis = berechne_praemie(5, 3) assert ergebnis.as_tuple().exponent == -2
pytest -v pytest --cov=src --cov-report=term-missing
Beobachte dabei zwei Dinge: Wie lange der gesamte Lauf dauert — bei Unit-Tests sollten es deutlich unter zwei Sekunden sein. Und was die Abdeckungsausgabe unter Missing anzeigt: Das sind die Zeilen, die nie ausgeführt wurden.
Zusatzaufgaben:
pytest vor jedem Commit ausführt.#!/usr/bin/env bash set -e echo "Tests laufen ..." pytest -q || { echo "Commit abgebrochen: Tests fehlgeschlagen." exit 1 }
chmod +x .git/hooks/pre-commit
Ein Test, der bei unverändertem Code mal durchläuft und mal fehlschlägt, ist schlimmer als kein Test — er zerstört das Vertrauen in die gesamte Suite. Sobald ein Team anfängt, fehlgeschlagene Läufe reflexartig zu wiederholen, ist die Testsuite als Sicherheitsnetz erledigt.
| Häufige Ursache | Abhilfe |
|---|---|
| Zeitabhängigkeit, echte Uhr | Zeit als Parameter übergeben oder einfrieren |
Feste Wartezeiten (sleep 2) | auf Bedingungen warten statt auf Sekunden |
| Reihenfolgeabhängigkeit | Tests unabhängig machen, Zustand zurücksetzen |
| Gemeinsame Testdatenbank | je Lauf eigenes Schema oder eigener Container |
| Zufallswerte ohne festen Startwert | Startwert setzen und protokollieren |
| Echte Netzwerkaufrufe | Doubles oder lokale Testdienste verwenden |
Regel: Ein sporadisch fehlschlagender Test wird sofort repariert oder abgeschaltet. Der Mittelweg — ihn stehen lassen und ignorieren — ist der einzige, der garantiert schadet.
| Anti-Pattern | Folge | Abhilfe |
|---|---|---|
| Eistüte statt Pyramide | Testlauf dauert 20 Minuten, ist unzuverlässig | Prüfungen nach unten verlagern |
| Abdeckung als Zielvorgabe | Tests ohne Zusicherungen | Abdeckung beobachten, nicht vorschreiben |
| Produktivdaten im Test | Datenschutzverstoß | synthetische Daten |
| Tests erst am Projektende | findet Fehler, wenn ihre Behebung am teuersten ist | Shift-Left |
| Sporadische Fehlschläge geduldet | Testsuite verliert ihre Funktion | sofort reparieren |
| Manuelle Regressionstests | skaliert nicht, ermüdet, wird übersprungen | automatisieren |
| Nur der Gutfall wird getestet | Fehlerbehandlung bricht in Produktion | Randfälle und Fehlerfälle mitnehmen |
Wenn du in deinem Betrieb eine einzelne Zeile Code änderst — wie lange dauert es, bis du weißt, ob du etwas kaputt gemacht hast?
Minuten sind gut. Stunden sind machbar. Tage bedeuten, dass die Rückmeldeschleife aus Kapitel 2 fehlt. Und „wir merken es, wenn jemand anruft„ ist die Antwort, die dieses Kapitel verhindern soll.
Weiter mit Kapitel 05 — Continuous Integration