Phase 3 von #134: Klassifizierung Unit / Integration / E2E / Property / Smoke mit Markern, Latenz-Budgets, Verzeichnis-Konventionen und Lauf-Befehlen. Index aktualisiert (0007 zwischen 0006 und 0008 eingefuegt — ADRs sind chronologisch, nicht numerisch sortiert).
5.9 KiB
0007 — Test-Taxonomie (Unit / Integration / E2E / Property / Smoke)
| Status | accepted |
| Datum | 2026-04-28 |
| Refs | #50 (Umbrella E2E Functional Acceptance), #51-54 (Sub-A-D), #134 (Phase 3 Audit), ADR 0003 |
Kontext
Die Test-Suite ist organisch ueber drei Epochen gewachsen:
- Original Unit-Suite (#46, #91) —
tests/conftest.pystubbtfitz/bs4/openai/pydantic_settings. Lokal in Sekunden lauffaehig, keine externen Calls, keine Live-Daten. - E2E Functional Acceptance (#50 Umbrella) —
tests/integration/mit eigenemconftest.py, das die Stubs nicht setzt. HTTP gegen echte Landtags-Portale, PDF-Parsing mit echtemfitz, DB-Lookups gegenembeddings.db. Markerintegration. - Playwright UI-Tests (#120) —
tests/e2e/test_ui.py, headless Chromium gegen die laufende App. Markere2e.
Mit dem Backfill aus #134 kamen zusaetzlich:
- Property-/Substring-Tests (ADR 0003) fuer LLM-Zitate gegen PDF-Seiten
- Smoke-Tests (
test_endpoints_smoke.py) — Endpoints nur auf Antwortcode + Format pruefen, kein Geschaeftslogik-Detail
Ohne klare Taxonomie weiss niemand, wo ein neuer Test hingehoert. Folge:
ad-hoc Tests werden in tests/ abgelegt, Marker werden vergessen, und
beim CI-Lauf brennen langsame Tests die schnellen mit ab.
Optionen
Option A — Flacher Test-Ordner ohne formale Kategorien
Status quo bis zu #50: alle Tests unter tests/, Marker frei waehlbar.
Vorteile: keine Migrationskosten, niedrige kognitive Last.
Nachteile: Kategorien implizit, Stub-Setup kollidiert mit echten
Imports, Lauf-Dauer schwankt unvorhersehbar.
Option B — Drei harte Verzeichnisse (tests/unit/, tests/integration/, tests/e2e/)
Strenge raeumliche Trennung mit jeweils eigenem conftest.py.
Vorteile: Stub-Konflikte ausgeschlossen, einfaches pytest tests/unit/.
Nachteile: grosse Migration; viele bestehende Test-Files muessten
in unit/ umziehen; verschachtelte Pfade werden vom Test-Runner und
von Reports etwas sperriger.
Option C — Flacher Ordner + verbindliche Marker (gewaehlt)
tests/ flach, aber jede Datei traegt einen klaren Kategorie-Marker
(integration, e2e, slow) oder ist Default-Unit. Neue
Sub-Verzeichnisse nur wenn sie strukturell notwendig sind (z.B.
tests/integration/ weil dort ein anderer conftest.py lebt — keine
Stubs).
Vorteile: wenig Migrationsschmerz, Default-Run laeuft schnell, aber
opt-in zu langsamen Suiten ist explizit (pytest -m integration).
Nachteile: Disziplin noetig, Marker mssen gepflegt werden.
Entscheidung
Option C mit folgender expliziter Taxonomie:
| Typ | Marker | Verzeichnis | Latenz | Was ist erlaubt |
|---|---|---|---|---|
| Unit | (none, default) | tests/*.py |
< 100 ms / Test | Reines Python, alle externen Dependencies gestubbed in tests/conftest.py. Domain-Logik, Validatoren, Pure Functions, Datenstrukturen. |
| Smoke | (none, default) | tests/test_*_smoke.py |
< 200 ms / Test | TestClient gegen app.main, nur Status-Code + Pflicht-Felder pruefen. Skipped wenn app.main nicht importierbar. |
| Property | (none, default) | tests/test_citations_substring.py u.a. |
< 500 ms / Test | Invarianten-Checks gegen Fixture-Corpus. Substrings, Strukturmuster. PDF-Parsing erlaubt, aber nur gegen Fixtures im Repo. |
| Integration | integration |
tests/integration/ |
< 5 s / Test, gesamt < 5 min | Echtes HTTP gegen Landtags-Portale, echtes fitz gegen reale PDFs, DB-Lookups gegen embeddings.db. Eigenes conftest.py ohne Stubs. Opt-in via pytest -m integration. |
| E2E | e2e |
tests/e2e/ |
< 30 s / Test | Headless-Chromium gegen lokal laufende App oder Prod-URL. Tests koennen flaky sein — werden NICHT von Default-Run getriggert. |
| Slow | slow |
(queruerend) | beliebig | Marker-Suffix zu jedem Typ. Ausschliessbar via pytest -m "not slow". Beispiel: ein Integration-Test, der pro BL einen Wahlprogramm-PDF herunterlaedt. |
Lauf-Konvention (verbindlich, in pytest.ini definiert):
pytest # Default — Unit + Smoke + Property, ~1s
pytest -m integration # nur E2E-Functional-Acceptance, ~5 min
pytest -m "integration and not slow" # E2E ohne PDF-Downloads
pytest -m e2e # nur Playwright-UI-Tests
pytest -m "" tests/ # ALLES (auch lokal selten gebraucht)
Naming-Konvention:
test_<modul>.py— Unit-Tests fuer ein Modultest_<feature>_smoke.py— Smoke-Teststest_<feature>_substring.py/_substring_*— Property-Tests- Integration- und E2E-Tests heissen wie das Feature, das sie testen
(z.B.
test_adapters_live.py,test_ui.py).
Konsequenzen
Positiv
- Default-Run bleibt schnell (< 2s) — niemand wartet bei jedem Save.
- Klar, wo neue Tests landen: jeder neue Test im Default-Ordner ist
ein Unit-Test mit Stubs; alles, was Live-HTTP/PDF/LLM braucht,
geht zwingend nach
tests/integration/. - CI kann Default-Suite als Pre-Commit-Gate nutzen, Integration-Suite nightly oder pre-deploy.
Negativ
- Disziplin noetig: Marker vergessen → langsame Tests im Default-Run oder unbemerkte Lueckentest. Code-Review muss darauf achten.
- Smoke-Tests sind technisch keine Unit-Tests (importieren
app.main), aber wir behandeln sie wegen geringer Latenz als Default. Ausnahme bewusst akzeptiert.
Folgen fuer andere ADRs
- ADR 0003 (Sub-D Citation-Property-Tests) bleibt gueltig; Property-Tests sind hier explizit als eigene Kategorie verortet.
- Folge-Issue: Coverage-Baseline (
.coveragercmitfail_underpro Modul) — nicht im Skopus dieses ADRs, sondern eigenstaendig in Phase 3 von #134. - Folge-Arbeit: einzelne bestehende Test-Files umtaggen, falls sie
faktisch Integration sind aber als Unit liefen (Audit ergab: keine
bekannten Faelle, alle Live-Calls liegen in
tests/integration/).