Files
Projektwissen/docs/leitfaden/06_tests.md
T
DanielandClaude Sonnet 5 ec400ae59e Test-Audit: Datei:Zeile und Quelltext-Ausgabe für bequemes Ansehen/Editieren
status zeigt je Treffer "<Datei>:<Zeile>" (in Editor-Terminals anklickbar)
und optional per --code den Testquelltext im Terminal. Der Prüfstand selbst
bleibt ohne Quelltext, um Diff-Lärm bei jeder Codeänderung zu vermeiden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 12:45:12 +02:00

230 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tests: was geprüft wird, wie man es liest, was Pflicht ist
Stand 04.09.2026. Für alle, die an den LNO-Plugins mitarbeiten — auch für Fachleute ohne Programmiererfahrung.
Der Abschnitt 8 ist ausdrücklich für Sie geschrieben: dort geht es darum, zu **beurteilen**, ob eine Testsuite
fachlich das Richtige prüft. Dafür muss man keinen Code lesen können.
## 1. Warum das hier ernster genommen wird als sonst
Ein großer Teil des Codes entsteht mit KI-Agenten. Ein Agent kann überzeugend erklären, dass etwas funktioniert,
ohne dass es funktioniert. Er kann auch einen roten Test „reparieren", indem er die Erwartung an das falsche
Verhalten anpasst. Tests sind der einzige schnelle, nachprüfbare Nachweis, den ein Review hat.
Deshalb gilt: **Neue Funktionalität ohne Test wird nicht gemergt.** Und: Tests werden nicht gelöscht, nicht
übersprungen und nicht in ihrer Erwartung angepasst, um grün zu werden — außer das Verhalten wurde bewusst
geändert, und der Pull Request begründet es.
## 2. Die drei Ebenen
```mermaid
flowchart TB
M["<b>Manueller Test in QGIS</b><br/>wenige · Minuten bis Stunden · durch Menschen<br/><i>Bedienbarkeit, Optik, Zusammenspiel mit echten Daten</i>"]
Q["<b>QGIS-Tests</b> — geplant, siehe konzept/07<br/>einige Dutzend · Minuten · automatisch<br/><i>Layer, WFS, Drucklayouts, Docks gegen echtes QGIS</i>"]
K["<b>Mock-Tests</b> — heute 332 Stück<br/>viele · unter einer Sekunde · automatisch, ohne QGIS<br/><i>Logik, Parsing, Prüfregeln, Fehlerbehandlung</i>"]
M --- Q --- K
style M fill:#ffe9e9,stroke:#d94b2b
style Q fill:#e6f2ff,stroke:#2b7dd9
style K fill:#e8f6ec,stroke:#2f9e44
```
Je weiter unten, desto schneller, billiger und häufiger. Ein Fehler soll möglichst weit unten auffallen — dort
ist er in Sekunden zu finden statt in einer halben Stunde Klickerei.
**Mock-Tests** laufen ohne QGIS, weil aller Zugriff auf QGIS und Qt über die Wrapper in `sn_basis.functions`
läuft. Fehlt QGIS, liefern die Wrapper Ersatzobjekte. Das ist der Grund für die Wrapper-Regel in `AGENTS.md` —
sie ist nicht Selbstzweck, sondern das, was diese Ebene überhaupt möglich macht.
**QGIS-Tests** gibt es heute noch nicht automatisiert. Der Stufenplan steht in
[`docs/konzept/07_qgis_tests_plan.md`](../konzept/07_qgis_tests_plan.md).
**Manuelle Tests** bleiben unersetzbar für alles, was ein Mensch sehen muss: Sieht der Plan richtig aus? Ist der
Dialog bedienbar? Stimmen die Beschriftungen fachlich?
## 3. Was gehört auf welche Ebene
| Was geändert wurde | Mock-Test | QGIS-Test | Manuell |
|---|---|---|---|
| Rechenregel, Prüflogik, Schlüsselableitung | **Pflicht** | – | – |
| Einlesen einer Datei (Shape, Excel, CSV, ZIP) | **Pflicht** | wenn Geometrie entsteht | – |
| Fehlerbehandlung, Sonderfälle, leere Eingaben | **Pflicht** | – | – |
| Aufruf eines Webdienstes (WFS, VLN, ALKIS) | **Pflicht** (Antwort nachgestellt) | empfohlen | bei neuem Dienst |
| Layer anlegen, stylen, in den Baum hängen | soweit möglich | **Pflicht** | – |
| Drucklayout, Planausgabe | Logik ja | **Pflicht** | **Pflicht** (Aussehen) |
| Dialog, Dock, Tab, Signalverdrahtung | Logik dahinter | empfohlen | **Pflicht** |
| Menü, Toolbar, Plugin-Ladevorgang | – | empfohlen | **Pflicht** |
| Nur Dokumentation | – | – | – |
Faustregel: **Alles, was ohne sichtbare Oberfläche entscheidbar ist, gehört in einen Mock-Test.** Wenn sich etwas
schlecht testen lässt, liegt das meistens daran, dass Fachlogik und Oberfläche vermischt sind — dann ist die
Trennung die eigentliche Aufgabe, nicht der Test.
## 4. Die Pflicht im Detail
**Bei neuer Funktionalität:** mindestens ein Test je neuem beobachtbarem Verhalten. Nicht je Funktion — je
*Verhalten*. Eine Funktion, die drei Fälle unterscheidet, braucht drei Tests.
**Bei Änderungen an bestehender Logik:**
1. Vorher `python scripts/run_tests.py <paket> --baseline` laufen lassen und die Zahl merken.
2. Ändern.
3. Erneut laufen lassen. Keine Zeile `NEU ROT`.
4. Die Tests, die das geänderte Verhalten abdecken, erweitern oder anpassen — mit Begründung im Pull Request.
**Bei einer Fehlerbehebung:** zuerst einen Test schreiben, der den Fehler zeigt (also rot ist), dann beheben.
Sonst weiß niemand, ob der Fehler wirklich weg ist oder nur nicht mehr auffällt.
## 5. Wie ein guter Test aussieht
```python
def test_fbschl_mit_punkt_wird_erkannt(self):
"""Prüft, dass der Shape-Import auch das Feld 'FBSchl.' mit Punkt akzeptiert.
Manche DAVID-Exporte liefern den Feldnamen mit Punkt, andere ohne.
Beide müssen zum selben Gebietstyp führen.
"""
layer = self._layer_mit_feldern({"FBSchl.": "901/10110F"})
self.assertEqual(_ermittle_gebietstyp_aus_fbschl(layer), "Verfahrensgebiet")
```
Drei Anforderungen, alle drei verbindlich:
1. **Der Name sagt, was geprüft wird**, auf Deutsch, aus fachlicher Sicht:
`test_fbschl_mit_punkt_wird_erkannt` — nicht `test_case_3` oder `test_import`.
2. **Ein deutscher Docstring, erste Zeile in der Form `Prüft, dass …`.** Diese Zeile landet im Testkatalog
(Abschnitt 6) und ist für viele Leser der einzige Kontakt mit dem Test. Wenn nötig, danach ein Absatz mit dem
fachlichen Hintergrund.
3. **Ein Verhalten je Test.** Wenn im Namen ein „und" steht, sind es zwei Tests.
Weiter gilt: `unittest`, Datei `tests/test_<modul>.py`, kein Netzwerkzugriff, keine echten Verfahrensdaten, keine
Abhängigkeit von der Reihenfolge der Tests. Muster liefern die vorhandenen Tests des jeweiligen Pakets.
## 6. Der Testkatalog
```bash
python scripts/testkatalog.py
```
erzeugt [`docs/tests/testkatalog.md`](../tests/testkatalog.md): jeder Testfall aller Pakete mit seiner
Docstring-Zeile und seinem Status. Die Datei wird aus dem Code erzeugt und ist damit immer aktuell — sie wird
**nicht von Hand bearbeitet**.
Wozu das gut ist: Man sieht auf zwei Seiten, was die Suite abdeckt, ohne eine einzige Testdatei zu öffnen. Der
Abschnitt „Lücken" am Ende listet alle Tests ohne Docstring — das ist die Arbeitsliste, um den Katalog
vollständig lesbar zu machen.
Nach dem Hinzufügen von Tests den Katalog neu erzeugen und mit committen. `python scripts/testkatalog.py --pruefen`
meldet, wenn er veraltet ist.
## 7. Test-Audit: wann wurde ein Test zuletzt fachlich geprüft?
Der Testkatalog beantwortet „was prüft die Suite" und „läuft der Test durch" (Baseline, Abschnitt 8). Er
beantwortet **nicht** „hat das ein Mensch fachlich bestätigt". Ein Agent kann einen überzeugenden, aber
fachlich falschen Test schreiben (Abschnitt 1) — weder Docstring noch Baseline zeigen das. Das Test-Audit
schließt diese Lücke: `docs/status/testpruefungen.json` hält je Testfall Datum und Kurzprüfsumme der letzten
Prüfung fest. Ändert sich der Testcode danach, erkennt der Prüfstand das automatisch als **veraltet** — die
Prüfsumme stimmt nicht mehr überein.
Im Prüfstand (`docs/tests/testkatalog.html`) erscheint das als eigene Spalte/Pille je Testfall (unabhängig vom
Baseline-Status: ein grüner Test kann nie geprüft sein, ein roter Baseline-Test kann bereits geprüft sein) und
als eigene Filter-Chips „Nie geprüft" / „Veraltet geprüft".
```bash
python scripts/test_audit.py status sn_verfahrensgebiet # Prüfstatus je Testfall anzeigen
python scripts/test_audit.py status --nur ungeprueft # nur ungeprüfte Testfälle
python scripts/test_audit.py status sn_basis --muster TestX --code # mit Quelltext im Terminal
python scripts/test_audit.py markieren sn_basis TestX.test_y # als heute geprüft markieren
```
`markieren` nimmt exakte Bezeichner oder `fnmatch`-Muster (z. B. `"TestX.*"`) entgegen; `--trocken` zeigt die
Treffer, ohne zu schreiben — vor einem Muster mit vielen Treffern empfohlen. Das Skript importiert die
Katalog-Logik aus `scripts/testkatalog.py`, statt sie zu duplizieren.
`status` zeigt zu jedem Treffer „<Datei>:<Zeile>" — in den meisten Editor-Terminals anklickbar, um den Test
direkt zu öffnen und zu bearbeiten. Der Prüfstand selbst zeigt bewusst **keinen** Quelltext: er ist eine
versionierte, generierte Datei, und Quelltext darin würde bei jeder Codeänderung riesige, wenig aussagekräftige
Diffs erzeugen. Zum Lesen ohne Editor dient `--code`, zum Bearbeiten der übliche Weg über Konsole/Editor.
Abgrenzung zu Abschnitt 8 (Testkonzepte): Das Audit bestätigt, dass **vorhandene** Tests noch fachlich
zutreffen. Ob überhaupt genug getestet wird, zeigen die Testkonzept-Seiten — das ist der nächste, gesonderte
Schritt, kein Ersatz für das Audit.
## 8. Testkonzepte: prüfen, ob die Tests fachlich sinnvoll sind
Der Katalog beantwortet „was wird getestet". Er beantwortet **nicht** „ist das genug". Dafür gibt es je
wichtigem Fachablauf eine eigene Seite in [`docs/tests/`](../tests/), nach der Vorlage
[`docs/vorlagen/testkonzept.md`](../vorlagen/testkonzept.md).
Jede dieser Seiten hat denselben Aufbau:
1. **Ein Mermaid-Diagramm des fachlichen Ablaufs** — so, wie ein Sachbearbeiter ihn beschreiben würde.
Keine Klassennamen, keine Dateipfade.
2. **Eine Tabelle: Schritt → welcher Test deckt ihn ab → was bewusst nicht getestet wird, und warum.**
3. **Ein Abschnitt „Bekannte Lücken"** mit Issue-Nummer, wo etwas fehlt.
So können Sie als Fachperson prüfen, ohne Code zu lesen:
- Kommt jeder Schritt Ihres Ablaufs in der Tabelle vor? Fehlt einer, fehlt wahrscheinlich auch der Test.
- Sind die **Sonderfälle** dabei, die in der Praxis wirklich vorkommen — der leere Export, die doppelte
Gemarkung, der Datensatz aus dem Nachbarlandkreis?
- Steht bei „bewusst nicht getestet" eine Begründung, die Sie nachvollziehen können? „Zu aufwendig" ist keine.
- Beschreibt der Test das *Ergebnis* oder nur, dass etwas *durchläuft*? „Prüft, dass der Import nicht abstürzt"
ist deutlich weniger wert als „Prüft, dass aus 901/10110F der Gebietstyp Verfahrensgebiet wird".
Ein Beispiel liegt in [`docs/tests/verfahrensgebiet-shape-import.md`](../tests/verfahrensgebiet-shape-import.md).
Wenn Sie eine Lücke finden: Issue aufmachen (`python scripts/gitea.py entwurf issue Verfahrensgebiet` legt einen
Entwurf an). Eine gefundene Testlücke ist ein vollwertiges Ticket.
## 9. Die Baseline
Nicht alle Tests sind grün. Der Ist-Stand steht in [`docs/status/testbaseline.json`](../status/testbaseline.json)
und wirkt als Sperrklinke: **bekannte rote Tests dürfen bleiben, neue nicht dazukommen.**
```bash
python scripts/run_tests.py sn_verfahrensgebiet --baseline
```
- `NEU ROT` — nicht erlaubt. Ursache beheben, bevor der Pull Request aufgemacht wird.
- `neu grün` — schön. Im Pull Request nennen und eine Baseline-Aktualisierung vorschlagen.
- `unverändert gegenüber Baseline` — in Ordnung.
Die Baseline ändert **nur ein Maintainer** mit `--update-baseline`, nach Absprache. Ein Agent fasst sie nicht an.
Das Grünmachen der bekannten roten Tests läuft über eigene Issues, nicht nebenbei.
## 10. Wenn ein Test rot ist
Erst die Frage beantworten: **Ist der Test veraltet oder ist der Code falsch?**
| Befund | Was zu tun ist |
|---|---|
| Der Code verhält sich falsch | Code beheben. Der Test bleibt, wie er ist. |
| Das Verhalten wurde bewusst geändert (Issue deckt es ab) | Test anpassen, im Pull Request begründen, `changelog.txt` prüfen |
| Der Test prüft etwas, das es nicht mehr gibt | Test entfernen — mit Begründung und nur, wenn das Verhalten wirklich entfallen ist |
| `ModuleNotFoundError: sn_basis` | Nicht über `scripts/run_tests.py` gestartet, oder Klone fehlen |
| `AttributeError: _MockQgs… has no attribute` | Mock im Wrapper ergänzen — nicht den Test umgehen |
| Der Test hängt oder braucht Netz | Der Test ist falsch gebaut. Dienstantwort nachstellen. |
**Niemals** ein roter Test wird durch `@unittest.skip` oder durch Löschen grün gemacht. Das ist keine Lösung,
das ist das Verstecken des Problems vor dem nächsten Leser.
Details zum Deuten einzelner Fehler: Skill `sn-tests`.
## 11. Vor dem Pull Request
- [ ] `python scripts/run_tests.py <paket> --baseline` ohne `NEU ROT`
- [ ] Neue Funktionalität hat mindestens einen Test je neuem Verhalten
- [ ] Jeder neue Test hat einen deutschen Docstring `"""Prüft, dass …"""`
- [ ] `python scripts/testkatalog.py` neu erzeugt und mitcommittet
- [ ] Betrifft die Änderung einen dokumentierten Fachablauf: die Seite in `docs/tests/` nachgezogen
- [ ] Bei UI-, Layer-, WFS- oder Layoutverhalten: manueller QGIS-Test durchgeführt und im Pull Request
unter „Wie geprüft" beschrieben — oder ausdrücklich als offener Punkt benannt
## Siehe auch
- Skill `sn-tests` — Tests ausführen und Fehler deuten
- [`docs/konzept/07_qgis_tests_plan.md`](../konzept/07_qgis_tests_plan.md) — Ausbau zu echten QGIS-Tests
- [`docs/entscheidungen/0003-mock-tests-baseline-pflicht.md`](../entscheidungen/0003-mock-tests-baseline-pflicht.md)
- [`docs/entscheidungen/0009-tests-definition-of-done.md`](../entscheidungen/0009-tests-definition-of-done.md)
- [`docs/architektur/schnittstellen.md`](../architektur/schnittstellen.md) — die Wrapper, die den Mock-Modus tragen