docs(#134): ADR 0007 — Test-Taxonomie

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).
This commit is contained in:
Dotty Dotter 2026-04-28 02:04:24 +02:00
parent 3a8c03db6c
commit 7e20f910fe
2 changed files with 121 additions and 0 deletions

View File

@ -0,0 +1,120 @@
# 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:
1. **Original Unit-Suite** (#46, #91) — `tests/conftest.py` stubbt
`fitz`/`bs4`/`openai`/`pydantic_settings`. Lokal in Sekunden lauffaehig,
keine externen Calls, keine Live-Daten.
2. **E2E Functional Acceptance** (#50 Umbrella) — `tests/integration/`
mit eigenem `conftest.py`, das die Stubs *nicht* setzt. HTTP gegen
echte Landtags-Portale, PDF-Parsing mit echtem `fitz`, DB-Lookups
gegen `embeddings.db`. Marker `integration`.
3. **Playwright UI-Tests** (#120) — `tests/e2e/test_ui.py`, headless
Chromium gegen die laufende App. Marker `e2e`.
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):
```bash
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 Modul
- `test_<feature>_smoke.py` — Smoke-Tests
- `test_<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 (`.coveragerc` mit `fail_under` pro
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/`).

View File

@ -23,6 +23,7 @@ und Konsequenzen. Format inspiriert von [Michael Nygard](https://cognitect.com/b
| [0004](0004-deployment-workflow.md) | Docker Compose Deploy mit DB-/Reports-Volume und SN-XML-Sonderpfad | accepted | 2026-04-10 | | [0004](0004-deployment-workflow.md) | Docker Compose Deploy mit DB-/Reports-Volume und SN-XML-Sonderpfad | accepted | 2026-04-10 |
| [0005](0005-keycloak-sso-with-dev-bypass.md) | Keycloak SSO mit Dev-Bypass-Fallback | accepted | 2026-04-10 | | [0005](0005-keycloak-sso-with-dev-bypass.md) | Keycloak SSO mit Dev-Bypass-Fallback | accepted | 2026-04-10 |
| [0006](0006-embedding-model-migration-v3-to-v4.md) | Embedding-Modell-Migration text-embedding-v3 → v4 | accepted | 2026-04-11 | | [0006](0006-embedding-model-migration-v3-to-v4.md) | Embedding-Modell-Migration text-embedding-v3 → v4 | accepted | 2026-04-11 |
| [0007](0007-test-taxonomy.md) | Test-Taxonomie (Unit / Integration / E2E / Property / Smoke) | accepted | 2026-04-28 |
| [0008](0008-ddd-lightweight-migration.md) | DDD-Lightweight-Migration (Repository, LLM-Port, Domain-Verhalten) | accepted | 2026-04-20 | | [0008](0008-ddd-lightweight-migration.md) | DDD-Lightweight-Migration (Repository, LLM-Port, Domain-Verhalten) | accepted | 2026-04-20 |
## Wann ADR, wann nicht ## Wann ADR, wann nicht