Die Markierung per test_audit.py markieren bestätigt eine fachliche Abnahme durch einen Menschen. Agenten sichten und ergänzen Tests und schlagen Markierungen vor, markieren aber erst nach ausdrücklicher Freigabe. Aufgenommen in AGENTS.md (harte Regeln, Tests), docs/leitfaden/06_tests.md Abschnitt 7 und Skill sn-tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
249 lines
14 KiB
Markdown
249 lines
14 KiB
Markdown
# 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.
|
||
|
||
Ein zweiter Abschnitt, „Ungetestete Funktionen (strukturelle Lücke)", dreht die Blickrichtung um: Er listet
|
||
Funktionen und Methoden im **Quellcode**, deren Name in keiner Testdatei des Pakets vorkommt (Namensabgleich
|
||
per `ast`, ohne Import). Das ist bewusst **keine** Zeilen-Coverage in Prozent — die wurde in ADR 0009 als Ziel
|
||
verworfen, weil sie durchlaufene Zeilen statt fachliche Vollständigkeit misst. Es ist eine Lückenliste wie die
|
||
fehlenden Docstrings: ein Hinweis, wo ein Blick lohnt, kein Ausführungsnachweis und kein Ziel in Prozent.
|
||
|
||
Jede so gefundene Funktion gilt zusätzlich als **beabsichtigt** oder **ungeklärt**: Der Katalog liest dafür
|
||
den Docstring der Funktion und erkennt zwei Vermerke — `"""von Test abgesehen, weil …; Kürzel Datum"""` für
|
||
eine bewusst als vernachlässigbar eingestufte Funktion, `"""Test ausgesetzt, zunächst Refactor"""` für eine
|
||
Funktion, die erst nach einer geplanten Überarbeitung getestet wird. Fehlt der Vermerk, gilt die Funktion als
|
||
**ungeklärt** — das ist die eigentliche Arbeitsliste, nicht die beabsichtigten. Im Prüfstand
|
||
(`docs/tests/testkatalog.html`) erscheint das je Paket als eigene, aufklappbare Karte mit Kennzahlen
|
||
(„X ungeklärt, Y beabsichtigt"), genau wie bei den Testdateien.
|
||
|
||
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
|
||
```
|
||
|
||
**Markiert wird nur auf ausdrückliche Aufforderung eines Menschen.** Die Markierung sagt „fachlich bestätigt",
|
||
nicht „ein Agent hat es gelesen". Ein Agent sichtet die Tests, ergänzt Lücken und nennt die Testfälle, die er zur
|
||
Markierung vorschlägt; `markieren` (ohne `--trocken`) ruft er erst auf, wenn die Person die Markierung ausdrücklich
|
||
freigibt. Ein allgemeiner Auftrag wie „Audit fortsetzen" ist keine solche Freigabe.
|
||
|
||
`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
|