# 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_.py` — Unit-Tests fuer ein Modul - `test__smoke.py` — Smoke-Tests - `test__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/`).