| FOTO | AUTO | EDV | AUDIO |

04 — Testen und Qualität

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.

4.1 Warum Tests die Voraussetzung für Tempo sind

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.

4.2 Die Testpyramide

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.

Das Anti-Pattern: die Eistüte

         ╱────────────────────╲   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.

4.3 Testabdeckung — eine gefährliche Kennzahl

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.

4.4 Shift-Left

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

4.5 Shift-Right

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.

4.6 Testdaten und Datenschutz

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.

4.7 Lab: Tests für den KFZ-Rechner

cd kfz-rechner
python3 -m venv .venv && source .venv/bin/activate
pip install pytest pytest-cov
tests/test_praemie.py
"""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:

  1. Ergänze eine Funktion für einen Wenigfahrer-Rabatt und schreibe den Test zuerst. Lass ihn fehlschlagen, dann implementiere. Das ist testgetriebene Entwicklung im Kleinen.
  2. Baue absichtlich einen Fehler ein — etwa ein Vorzeichen — und prüfe, ob die Tests ihn finden. Falls nicht: Der Test fehlt.
  3. Richte einen Pre-Commit-Hook ein, der pytest vor jedem Commit ausführt.
.git/hooks/pre-commit
#!/usr/bin/env bash
set -e
echo "Tests laufen ..."
pytest -q || {
    echo "Commit abgebrochen: Tests fehlgeschlagen."
    exit 1
}
chmod +x .git/hooks/pre-commit

4.8 Flaky Tests

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.

4.9 Anti-Pattern

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

4.10 Reflexionsfrage

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