Projektwissen: Git-Vorgaben, Ticketzugriff, Tests und Einstieg

Erster Stand des Repositories AG_QGIS/Projektwissen (bisher Arbeitstitel
"Workspace", ADR 0006). Der lokale Ordner heisst weiter AG_QGIS, weil darin
auch die Plugin-Klone liegen.

Git verbindlich (ADR 0007), aus dem bisherigen Wiki-Konzept entwickelt:
- docs/leitfaden/05_git_arbeitsweise.md mit Kanal-Kette, Branch-Namen und
  Gitea-Branch-Schutz.
- Der Einstiegspunkt eines Fixes richtet sich nach dem Kanal, in dem der
  Fehler sitzt; ein Fix an testing oder stable ist erst mit der
  Rueckfuehrung nach unten abgeschlossen. Das fehlte bisher.
- Der stabile Kanal heisst stable, nicht main: der Release-Workflow in
  Repository:hidden/workflows fuehrt git checkout "$CHANNEL" aus, der
  Branchname muss also dem Kanalnamen entsprechen. Er existiert in keinem
  Plugin-Repo und ist vom Maintainer anzulegen.
- Der Mensch drueckt ab: .claude/settings.json sperrt push, merge, tag.
- scripts/hallo.py und /hallo holen den Stand aller Klone, per
  Fast-Forward wo gefahrlos; nie mergen, stashen oder verwerfen.

Ticketzugriff:
- scripts/gitea.py liest Issues, Kommentare und PRs ueber die oeffentliche
  API, ohne Token, nur GET. Schreibendes nur als Entwurf in entwuerfe/.
- Skill sn-ticket.

Tests (ADR 0009):
- docs/leitfaden/06_tests.md: Testebenen, Testpflicht, und wie eine
  Fachperson beurteilt, ob Tests fachlich sinnvoll sind.
- scripts/testkatalog.py erzeugt docs/tests/testkatalog.md per ast-Parsing,
  ohne Import und ohne QGIS. Befund: nur 100 von 332 Tests haben einen
  Docstring, sn_basis keinen einzigen von 87.
- Vorlage und erste Testkonzept-Seite mit Mermaid. Dabei gefunden und gegen
  origin geprueft: _ermittle_gebietstyp_aus_fbschl hat keinen Test, obwohl
  PR #44/#45 genau diese Funktion tolerant gemacht hat.
- docs/konzept/07_qgis_tests_plan.md: Stufenplan fuer Tests gegen echtes
  QGIS. Dabei aufgefallen: QGIS 3.40 ist seit Januar 2026 End of Life,
  qgis_min steht aber noch darauf.

Werkzeuge (ADR 0008):
- 04_werkzeuge_einrichten.md nur noch Claude Code als CLI und in VS Code.
- .vscode/ entfernt, brauchbare Einstellungen als persoenliche Vorlage.

Ausserdem: README als Einstieg neu aufgebaut, Mermaid-Diagramm in
schnittstellen.md repariert (unmaskierte Klammern, rendert seither nicht).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 11:57:26 +02:00
co-authored by Claude Sonnet 5
commit ab416fb34c
57 changed files with 7202 additions and 0 deletions
@@ -0,0 +1,188 @@
# Konzept: Gemeinsame KI-gestützte Entwicklung über mehrere Behörden und Werkzeuge
Stand 02.09.2026. Vorschlag zur Abnahme durch die AG QGIS. Die Umsetzung liegt als Struktur bereits in diesem
Workspace vor; was das Team anders entscheidet, wird hier und in `docs/entscheidungen/` nachgezogen.
## 1. Ausgangslage und Ziele
**Randbedingungen**
- Mehrere Entwickler in verschiedenen Flurbereinigungsbehörden, überwiegend Fachleute ohne Software-Ausbildung.
- Jeder nutzt eigene KI-Agenten: Claude Code, OpenAI Codex, teils GitHub Copilot in VS Code (Issue Basis #36).
*Nachtrag 04.09.2026: Das Team arbeitet mit Claude Code; die Doku beschreibt nur noch dieses Werkzeug, die
Dateistruktur bleibt werkzeugneutral (ADR 0008).*
Alle arbeiten ordnerbasiert und lesen Anweisungsdateien aus dem Repository.
- Elf Repositories in einer Gitea-Organisation, eigener Release-Prozess, Zielplattform Windows/OSGeo4W mit QGIS 3.40
und 4. Die Agenten laufen meist ohne QGIS.
- Der Code ist gewachsen und wird später grundlegend refaktoriert; bis dahin soll die Arbeit trotzdem sicher und
effizient sein.
**Ziele**
1. Jeder Agent und jeder Entwickler bekommt **denselben Projektkontext**, unabhängig vom Werkzeug.
2. Wissen fließt **zurück ins Repository**, nicht in Chatverläufe oder lokale Gedächtnisse.
3. **Tokensparsamkeit**: Kontext wird schichtweise geladen; Verifikation (Tests) ist billig und schnell.
4. **Verbindlicher, leichter Arbeitszyklus**, der für Fachleute in einer Stunde erlernbar ist.
5. Vorbereitung des Refactorings, ohne es vorwegzunehmen.
## 2. Leitprinzipien
- **Eine Wahrheit, versioniert.** Alles, was mehr als eine Person wissen muss, steht in Git. Werkzeugspezifische
Dateien sind nur dünne Zeiger auf die kanonische Quelle.
- **Kontext in Schichten (progressive Offenlegung).** Immer geladen: eine kurze Anweisungsdatei. Auf Abruf: Modulkarte,
Schnittstellen, Fallstricke. Bei Bedarf: Skills mit Ablaufwissen. Nur wenn nötig: der Code selbst.
- **Verträge vor Code.** Agenten arbeiten sicher, wenn sie die impliziten Regeln kennen (Wrapper, Variablen, Dock,
Release). Deshalb sind Verträge ausdrücklich dokumentiert und in Regeln übersetzt.
- **Verifikation ist Pflicht und billig.** Mock-Tests in Sekunden, Baseline gegen Verschlechterung, QGIS-Tests dort,
wo Mocks nicht reichen.
- **Kleine Schritte, sichtbare Übergaben.** Ein Issue, ein Branch, ein PR, eine Übergabenotiz. Keine Sammel-PRs.
- **Menschen entscheiden, Agenten arbeiten.** Fachliche Entscheidungen und Merges bleiben bei Personen; Agenten
liefern Vorschläge mit Nachweis.
## 3. Struktur des Projekts: drei Varianten
| Variante | Beschreibung | Vorteile | Nachteile |
|---|---|---|---|
| **A. Workspace-Meta-Repository mit Klonen** (empfohlen, umgesetzt) | Neues Repo `AG_QGIS/Projektwissen` mit `AGENTS.md`, `docs/`, Skills, Skripten, Manifest. Plugin-Repos werden als Unterordner geklont (per `.gitignore` ausgenommen). | Kein Eingriff in bestehende Repos und Release-Prozess; ein Ordner = ganzer Projektkontext; sofort nutzbar; Meta-Repo versioniert die Wissensbasis; Manifest hält kompatible Stände fest | Zwei Ebenen von Git; Entwickler müssen im Workspace-Root starten; per-Repo-Dateien bleiben nötig für Einzelklone |
| B. Monorepo | Alle Plugins in ein Repository überführen | Ein Kontext, ein PR über Plugin-Grenzen, einfache Verträge | Release-Prozess, Tags und Feeds müssten umgebaut werden; Historie-Migration; das ist Teil des Refactorings, nicht der Vorbereitung |
| C. Nur per-Repo-Dateien | `AGENTS.md` in jedem Plugin-Repo, keine Meta-Ebene | Minimal | Gesamtkontext fehlt genau dort, wo er gebraucht wird (Basis ↔ Fachplugin); Wissensbasis müsste dupliziert werden |
**Empfehlung A**, mit per-Repo-`AGENTS.md` als Ergänzung (Vorlage in `docs/vorlagen/AGENTS.repo.md`). Variante B bleibt als
Option für die Refactoring-Phase offen; der Workspace bereitet sie vor (Manifest, Verträge, Tests).
## 4. Anweisungsdateien: kanonisch `AGENTS.md`
| Werkzeug | liest | Lösung |
|---|---|---|
| Claude Code (genutzt) | `CLAUDE.md` (mit `@`-Importen), Skills in `.claude/skills/` | `CLAUDE.md` = `@AGENTS.md` + wenige Claude-Zeilen; Skills werden gespiegelt |
| andere (Codex, Copilot, Cursor, Gemini CLI, …) | überwiegend `AGENTS.md` und Agent-Skills-Standard | direkt, ohne dass wir etwas ändern müssten – wir dokumentieren die Einrichtung aber nicht (ADR 0008) |
Alternativen und warum nicht: getrennte Dateien je Werkzeug (Drift), Symlink `CLAUDE.md → AGENTS.md` (unter Windows
Administratorrechte nötig; Import ist gleichwertig).
**Inhalt und Grenzen der `AGENTS.md`:** unter 150 Zeilen; nur Fakten, die in jeder Sitzung gelten (Regeln, Befehle,
Leseliste, Arbeitsablauf in Kurzform). Alles Prozedurale wandert in Skills, alles Beschreibende in `docs/`.
Sie wird bei jeder Sitzung in den Kontext geladen und ist damit die teuerste Datei des Projekts.
**Per-Repo-`AGENTS.md`** (kurz, ≤ 40 Zeilen): Rolle des Repos, Aufbau, Zusatzregeln, Testbefehl, Verweis auf den
Workspace. Nötig, weil Codex Anweisungen nur bis zur Wurzel des jeweiligen Git-Repos sucht und weil Entwickler
gelegentlich nur ein Repo klonen. Für Claude Code liegt daneben eine `CLAUDE.md` mit `@AGENTS.md`.
## 5. Bausteine der Wissensbasis
```
AG_QGIS/ Klon von Projektwissen (Meta)
├── AGENTS.md, CLAUDE.md Schicht 1: immer geladen
├── workspace.toml Manifest: Repos, Branches, Paketnamen, Rollen
├── scripts/ setup_workspace.py, run_tests.py, link_qgis_profile.py
├── .agents/skills/<name>/SKILL.md Schicht 3: Ablaufwissen auf Abruf
├── docs/status/STATUS.md Schicht 2: aktueller Stand (Pflichtlektüre)
├── docs/architektur/ Modulkarte, Schnittstellen (lebend)
├── docs/wissen/ Fallstricke, Glossar, externe Dienste (geteiltes Gedächtnis)
├── docs/entscheidungen/ ADRs: warum etwas so ist
├── docs/vorlagen/ Issue, PR, Übergabe, Repo-AGENTS.md, CI-Workflow
├── docs/analyse|konzept|leitfaden Momentaufnahmen und stabile Texte
└── Plugin_SN_*/ … eigenständige Klone, je mit AGENTS.md + CLAUDE.md
```
| Baustein | Zweck | Pflegeregel |
|---|---|---|
| `STATUS.md` | Was ist gerade los, wer arbeitet woran, offene Baustellen | bei jedem Merge/Übergabe |
| `modulkarte.md` | gezieltes Lesen statt Vollzugriff; Kennzeichnung großer Dateien | bei neuen/verschobenen Modulen |
| `schnittstellen.md` | die impliziten Verträge explizit | vor jeder Änderung daran |
| `fallstricke.md` | nicht ableitbares Wissen mit Datum und Quelle | sobald etwas Zeit gekostet hat |
| `glossar.md`, `externe_dienste.md` | Fachbegriffe, URLs, Abhängigkeiten | nach Bedarf |
| `entscheidungen/NNNN-*.md` | eine Entscheidung je Datei: Kontext, Entscheidung, Folgen, Status | neue Entscheidung = neue Datei; alte nie ändern, nur ersetzen |
| Skills | wiederkehrende Abläufe: Aufgabe bearbeiten, Tests, Übergabe, neues Fachplugin, Release | wenn sich ein Ablauf ändert |
| `testbaseline.json` | Liste bekannter roter Tests | nur mit Absprache (`--update-baseline`) |
### Shared Memory: was wohin
| Art von Wissen | Beispiel | Ort |
|---|---|---|
| Regel, die immer gilt | „Qt nur über Wrapper“ | `AGENTS.md` |
| Vertrag zwischen Komponenten | Prioritäten der Actions, Variablennamen | `schnittstellen.md` |
| Stolperfalle | „Release baut Kanal-Branch, nicht Tag“ | `fallstricke.md` |
| Warum-Entscheidung | „AGENTS.md kanonisch“ | `entscheidungen/` |
| Ablauf | Release-Schritte | Skill |
| Zwischenstand einer Aufgabe | „Test X ist rot, weil …“ | Übergabenotiz im PR und `STATUS.md` |
| Persönliche Vorlieben, lokale Pfade | Editor, OSGeo-Pfad | lokales Tool-Gedächtnis, `CLAUDE.local.md` (gitignored) |
Die werkzeugeigenen Gedächtnisse (Claude Auto-Memory) bleiben erlaubt, gelten aber als
**Notizzettel**. Die Regel in `AGENTS.md` und im Skill `sn-uebergabe` verpflichtet dazu, Teilbares in die Wissensbasis
zu überführen. Das greift den Vorschlag aus Issue Basis #36 auf und ersetzt die dort vorgesehenen
`copilot-instructions.md` je Plugin durch die werkzeugneutralen `AGENTS.md`.
## 6. Tokensparsamkeit: konkrete Maßnahmen
| Maßnahme | Wirkung | Umsetzung |
|---|---|---|
| Kurze `AGENTS.md`, Rest auf Abruf | jede Sitzung startet mit wenigen tausend Token statt Vollkontext | erledigt |
| Modulkarte mit Zeilenzahlen und Warnung ab 600 Zeilen | Agent liest Abschnitte statt Dateien | erledigt; Regel in `AGENTS.md` |
| Schnittstellen-Dokument | keine Erkundung von drei Repos, um einen Vertrag zu verstehen | erledigt |
| Fallstricke | keine Wiederholung bekannter Irrwege | erledigt, wächst |
| Skills statt wiederholter Erklärungen | Ablauf wird nur geladen, wenn er gebraucht wird | 5 Skills erledigt |
| `run_tests.py` mit Zusammenfassung und Baseline | Sekunden statt Minuten; keine Rohausgaben im Kontext | erledigt |
| Übergabenotiz je Aufgabe | nächste Sitzung beginnt bei Schritt 5 statt 1 | Vorlage + Skill erledigt |
| Issues mit Abnahmekriterien | weniger Rückfragen, weniger Fehlversuche | Vorlage erledigt |
| Unteragenten für breite Suchen (Claude Code) | Hauptkontext bleibt schlank | Hinweis in `CLAUDE.md` |
| Refactoring großer Dateien | dauerhaft kleinere Lesekosten | spätere Phase |
Anti-Muster, die vermieden werden: ganze Dateien in den Chat kopieren; „Lies das ganze Projekt“; Testrohausgaben
einfügen; parallele Erkundung durch mehrere Personen ohne Übergabe; Erkenntnisse nur im Chat.
## 7. Arbeitszyklus und Rollen
```mermaid
flowchart LR
I[Issue mit Abnahmekriterien] --> B[Branch von unstable]
B --> A[Agent-Auftrag mit Kontextverweisen]
A --> U[Umsetzung in kleinen Schritten]
U --> T[run_tests.py --baseline\n+ QGIS-Test bei UI]
T --> P[PR mit Übergabenotiz]
P --> R[Review durch Mensch]
R -->|Änderungen| U
R -->|Merge| S[STATUS.md, Wissen, Changelog]
S --> REL[Release bei Bedarf]
```
| Rolle | Aufgaben |
|---|---|
| **Maintainer** (je Repo eine Person, Vorschlag: bisherige Hauptentwickler) | Merge-Recht, Release, Pflege von `AGENTS.md`/Verträgen, Baseline-Änderungen |
| **Entwickler** | Issues formulieren, Agenten steuern, PRs mit Nachweis liefern, Wissensbasis pflegen |
| **Agent** | umsetzen, testen, dokumentieren; keine Merges, keine Tags, keine Entscheidungen über Verträge |
| **AG QGIS** | Entscheidungen zu Verträgen, Datenmodell, Refactoring, Alternativansatz |
Review-Regeln: KI-erzeugter Code wird immer von einer Person gelesen; PRs unter etwa 400 geänderten Zeilen; jede
Vertragsänderung braucht ein Issue mit Label `Schnittstelle`.
## 8. Werkzeugspezifika
**Claude Code:** im Workspace-Root starten; `/context` zeigt, ob `CLAUDE.md` und `AGENTS.md` geladen sind. Skills
liegen nach `python scripts/setup_workspace.py --nur-skills` in `.claude/skills/`. Beim Arbeiten in einem
Plugin-Ordner lädt Claude die dortige `CLAUDE.md` automatisch dazu. Auto-Memory ist lokal; Regel siehe Abschnitt 5.
**Gitea:** Labels `Bug`, `Feature`, `Diskussion` ergänzen um `Schnittstelle`, `Tests`, `Doku`, `Refactoring`.
Issue- und PR-Vorlagen aus `docs/vorlagen/` in `.gitea/` der Repos übernehmen (optional).
## 9. Einführung in Phasen
| Phase | Inhalt | Ergebnis |
|---|---|---|
| **0 – Projektwissen veröffentlichen** | Repo `AG_QGIS/Projektwissen` anlegen, diesen Ordner pushen, Team liest Konzept und Leitfaden, Entscheidungen bestätigen (ADR-Status auf „angenommen“) | ab sofort gemeinsamer Kontext |
| **1 – Repos anbinden** | per-Repo `AGENTS.md`/`CLAUDE.md` per PR in Basis, Plan41, Verfahrensgebiet, Widmung; Release-Excludes ergänzen; Metadata-Templates korrigieren | Agenten arbeiten auch in Einzelklonen korrekt |
| **2 – Tests absichern** | Baseline committen, CI-Workflow aus Vorlage in die drei Plugin-Repos, Rot→Grün als Issues je Testdatei | Tests haben wieder Aussagekraft |
| **3 – Verträge festschreiben** | Abhängigkeiten deklarieren, Variablennamen bereinigen (kleiner, gezielter Eingriff), Linkliste-Entscheidung, Manifest kompatibler Versionen | stabile Basis für den Umbau |
| **4 – Refactoring planen** | Zielarchitektur (inkl. Fachschale-Idee), Scheiben, Reihenfolge, Monorepo-Frage | eigenes Konzept |
Phase 0 und 1 sind in wenigen Stunden erledigt; Phase 2 ist die erste echte Teamaufgabe mit Agenten und zugleich das
Training des Arbeitszyklus.
## 10. Entscheidungen, die das Team treffen muss
1. Name und Ort des Meta-Repositories (entschieden: `AG_QGIS/Projektwissen`, ADR 0006).
2. Bestätigung der ADRs 0001–0005 (`docs/entscheidungen/`).
3. Maintainer je Repository.
4. Bestätigung der ADRs 0006–0009 (Reponame, Branchmodell, Werkzeugfokus, Testpflicht).
5. Linkliste: zentrale CSV als einzige Quelle (Empfehlung) oder XLSX im Plugin.
6. Zeitpunkt des Refactorings (Empfehlung: nach Phase 3) und ob die Fachschale-Idee Teil des Zielbilds wird.
+244
View File
@@ -0,0 +1,244 @@
# Plan: automatisierte Tests gegen echtes QGIS
Stand 04.09.2026. Vorschlag zur Abstimmung im Team. Ergänzt [`../leitfaden/06_tests.md`](../leitfaden/06_tests.md)
um die zweite Testebene. Recherchestand und Quellen am Ende.
## 1. Worum es geht
Heute laufen 332 Tests im Mock-Modus — ohne QGIS, in unter einer Sekunde. Das deckt Logik, Parsing und
Prüfregeln ab. Es deckt **nicht** ab, was erst im Zusammenspiel mit QGIS passiert: ob ein Layer wirklich entsteht,
ob ein WFS-Ergebnis verarbeitbar ist, ob ein Drucklayout eine Seite erzeugt, ob ein Dock sich sauber wieder
abmeldet. Genau dort sitzen die Fehler, die heute nur durch manuelles Klicken auffallen — und die zwischen Qt5
und Qt6 unterschiedlich ausfallen.
Es gibt seit Jahren einen etablierten Weg, QGIS-Plugins ohne Bildschirm und ohne menschliche Interaktion gegen
eine echte QGIS-Instanz zu testen. Dieser Plan beschreibt, wie wir dort in vier Stufen hinkommen.
> **Der Mock-Modus bleibt.** Er wird nicht ersetzt. Er ist die schnelle Ebene, die bei jedem Commit läuft;
> QGIS-Tests sind die langsame Ebene, die bei jedem Pull Request läuft. Wer die Mocks abschafft, tauscht
> Sekunden gegen Minuten und verliert die Wrapper-Disziplin, die den Qt5/Qt6-Code zusammenhält.
## 2. Vorab: unsere Zielversionen stimmen nicht mehr
Recherchestand 04.09.2026, geprüft am QGIS-Release-Fahrplan und an den Docker-Tags:
| | Version | Status |
|---|---|---|
| LTR | **3.44** | aktueller Langzeit-Release |
| Stable | **4.2** | wird am 25.09.2026 zum LTR |
| 3.40 | 3.40.15 (Januar 2026) | **End of Life**, keine Updates mehr |
`workspace.toml` sagt `qgis_min = "3.40"`, alle `.gitea/templates/metadata.template` sagen
`qgisMinimumVersion=3.40`. Das ist keine Testfrage, sondern eine Produktentscheidung: **Welche QGIS-Versionen
unterstützen wir?** Sie muss vor Stufe 2 fallen, weil sie die Test-Matrix bestimmt. Vorschlag zur Diskussion:
`3.44` als Minimum, `4.2` als zweites Matrix-Ziel, `3.40` nur solange Bestandsinstallationen es erzwingen.
## 3. Wie es technisch funktioniert
Der Kern ist `qgis.testing.start_app()` aus dem QGIS-Quellbaum. Es baut eine vollständige `QgsApplication` mit
registrierten Datenprovidern auf, in einem temporären Profil, und ist idempotent — mehrfacher Aufruf schadet
nicht. Plugins lädt es bewusst **nicht**; das eigene Plugin instanziiert der Test selbst.
```python
import os
if not os.getenv("DISPLAY"):
os.environ["QT_QPA_PLATFORM"] = "offscreen" # muss VOR dem App-Start gesetzt sein
from qgis.testing import start_app, unittest
from qgis.testing.mocked import get_iface
QGIS_APP = start_app()
IFACE = get_iface() # echtes QMainWindow, echter QgsMapCanvas, echter LayerTree
```
`get_iface()` liefert kein selbstgebautes Mock, sondern ein `QgisInterface`-Objekt mit echtem Hauptfenster,
echtem Kartenfenster und echtem Layerbaum. Das ist deutlich realitätsnäher als alles, was wir selbst nachbauen
könnten, und es kostet uns keine Pflege.
`QT_QPA_PLATFORM=offscreen` ist der Schalter, der das Ganze ohne Bildschirm laufen lässt — auf dem CI-Server
genauso wie unter Windows.
## 4. Der Stufenplan
```mermaid
flowchart LR
S1["<b>Stufe 1</b><br/>start_app() lokal<br/><i>klein</i>"]
S2["<b>Stufe 2</b><br/>Gitea Actions<br/>Matrix 3.44 / 4.2<br/><i>mittel</i>"]
S3["<b>Stufe 3</b><br/>pytest-qgis<br/><i>klein</i>"]
S4["<b>Stufe 4</b><br/>Render-Vergleich<br/>Plan41<br/><i>groß</i>"]
S1 --> S2 --> S3 --> S4
style S1 fill:#e8f6ec,stroke:#2f9e44
style S2 fill:#e6f2ff,stroke:#2b7dd9
style S3 fill:#e6f2ff,stroke:#2b7dd9
style S4 fill:#fff4e6,stroke:#d9822b
```
### Stufe 1 — QGIS-Tests lokal, ohne CI
**Ziel:** Eine erste echte QGIS-Suite je Plugin, die ein Entwickler auf seinem Windows-Rechner starten kann.
- Neuer Ordner `tests/qgis/` je Plugin-Repo, getrennt von den Mock-Tests in `tests/`. Beide Suiten bleiben
unabhängig lauffähig.
- Start unter Windows über die OSGeo4W-Umgebung:
`C:\OSGeo4W\bin\python-qgis-ltr.bat -m pytest tests\qgis -v`.
Ein normales `python -m pytest` findet die QGIS-Bindings nicht.
- Die vorhandenen Skripte `tests/test_qgis.bat` und `tests/start_osgeo4w_qgis.bat` werden dabei ersetzt. Sie
enthalten heute fest verdrahtete Pfade (`D:\OSGeo`, `D:\QGISQT5`) und funktionieren nur auf einem Rechner.
- Erste Kandidaten, weil dort der Mock-Modus am wenigsten aussagt: Layer anlegen und stylen (`sn_basis`),
Verarbeitung einer WFS-Antwort (`sn_verfahrensgebiet`), Erzeugen eines Drucklayouts (`sn_plan41`).
**Aufwand:** klein. Kein neues Werkzeug, keine Infrastruktur.
**Nutzen:** sofort, aber nur für die, die es lokal ausführen.
### Stufe 2 — Gitea Actions mit Docker
**Ziel:** Jeder Pull Request wird automatisch gegen zwei QGIS-Generationen geprüft, ohne dass jemand eine zweite
QGIS-Version installiert.
Das ist der größte Hebel des ganzen Plans. Der Qt5/Qt6-Unterschied ist in `fallstricke.md` ein Dauerthema, und
er lässt sich mit Bordmitteln kaum abdecken — hier fällt er automatisch auf.
Die offiziellen Images `qgis/qgis` bringen QGIS samt Bindings mit. Verifizierte Tags (Stand 04.09.2026):
`3.44`, `3.44.14`, `4.0`, `4.2`, `4.2.2`, `ltr`, `stable`. QGIS 4 gibt es nur auf Qt6-Distributionen
(Debian trixie, Ubuntu questing) — kein `4.x-noble`.
```yaml
name: QGIS-Tests
on:
pull_request:
branches: [unstable, testing, stable]
jobs:
qgis:
runs-on: ubuntu-latest # Label muss auf einen Docker-fähigen act_runner zeigen
strategy:
fail-fast: false
matrix:
qgis: ["3.44", "4.2"]
steps:
- uses: actions/checkout@v4
- name: Tests in QGIS ${{ matrix.qgis }}
run: |
docker run --rm -v "$PWD":/src -w /src \
-e QT_QPA_PLATFORM=offscreen \
qgis/qgis:${{ matrix.qgis }} \
bash -c "python3 -m venv /venv --system-site-packages \
&& /venv/bin/pip install -q pytest \
&& /venv/bin/pytest tests/qgis -v"
```
**Zu klären vor der Umsetzung:**
- Der vorhandene Gitea-Runner trägt das Label `alpine-latest` (siehe `docs/vorlagen/gitea_workflow_tests.yaml`).
Für Docker-in-Docker braucht es einen Runner mit Docker-Zugriff. Das ist eine Frage an den Maintainer der
Runner-Infrastruktur, keine Codefrage.
- Die Fachplugins brauchen im Container auch `sn_basis`. Das Muster steckt schon in
`docs/vorlagen/gitea_workflow_tests.yaml`: die Nachbar-Repos dazuklonen und unter ihren Paketnamen ablegen.
**Aufwand:** mittel, im Wesentlichen Infrastruktur.
**Nutzen:** hoch und dauerhaft.
### Stufe 3 — `pytest-qgis`
**Ziel:** Die bekannten Stolperfallen von QGIS-Tests nicht selbst lösen müssen.
`pytest-qgis` (Version 4.1.1, August 2026, Python ≥ 3.10, QGIS ≥ 3.34, Qt6-tauglich seit 3.0.0) bringt mit:
| Was | Warum wichtig |
|---|---|
| Fixture `qgis_app` | startet die App einmal je Sitzung |
| Fixture `qgis_iface` | patcht `qgis.utils.iface` automatisch — sonst ist es `None` |
| Fixture `qgis_new_project` | leeres Projekt je Test; sonst wirken Layer aus Test A in Test B nach |
| automatisches Layer-Aufräumen | verhindert die bekannten Abstürze beim Beenden |
| Fixture `qgis_processing` | Processing-Registry initialisiert |
| `--qgis_disable_exit` | Notausgang, falls es beim Beenden doch kracht |
**Aufwand:** klein — etwa eine halbe Stunde plus Umstellung der bis dahin geschriebenen Tests.
**Empfehlung:** erst nach Stufe 2, damit die Umstellung einmal an einer laufenden CI verifiziert wird.
### Stufe 4 — Render-Vergleich für Plan41
**Ziel:** Erkennen, wenn sich das Aussehen eines Plans ungewollt ändert.
Für ein Plugin, dessen Zweck das Zeichnen von Plänen nach § 41 FlurbG ist, ist das fachlich der wertvollste
Testtyp — und der teuerste. Mit einem verifizierten Fallstrick:
> `QgisTestCase.render_layout_check()` und `render_map_settings_check()` suchen Referenzbilder über
> `setControlPathPrefix()` in einem zur Compile-Zeit einkompilierten Pfad des QGIS-Quellbaums. In einer
> installierten QGIS-Version existiert dieser Pfad nicht. Die bequemen Helfer sind für Plugins **nicht** nutzbar.
Gangbar sind stattdessen:
- `QgsLayoutExporter.renderPageToImage()` und ein eigener, toleranter Bildvergleich, oder
- `QgsRenderChecker` direkt, das als einziges `setControlImagePath()` anbietet.
**Bedingungen, ohne die es nur Ärger macht:**
- Referenzbilder **im CI-Container** erzeugen, nie auf einem Entwickler-Windows. Schriftrendering und
Antialiasing unterscheiden sich zwischen den Plattformen und erzeugen sonst dauerhaft rote Tests.
- Toleranz einplanen (Farbabweichung und Anteil abweichender Pixel), nicht auf Byte-Gleichheit prüfen.
- Mit **zwei bis drei** Referenzplänen anfangen, nicht mit fünfzig.
**Aufwand:** groß. Erst angehen, wenn Stufen 1–3 stabil laufen.
## 5. Was wir bewusst nicht machen
| Ansatz | Warum nicht |
|---|---|
| YAML-Testframework für Processing-Algorithmen | An `nose2`, `utilities.py` und CTest im QGIS-Quellbaum gebunden. Für externe Plugins nur mit Portierungsaufwand nutzbar. Stattdessen `processing.run(...)` und `assertLayersEqual`. |
| `qgis_setup.sh` / `qgis_testrunner.sh` (Plugin im echten QGIS laden) | Kein pytest-Reporting; Erfolg wird über String-Suche in der Standardausgabe erkannt. Nur nötig, wenn der Plugin-Ladevorgang selbst geprüft werden soll. |
| `pytest-xdist` (Parallelisierung) | Bei unserer Teamgröße und Suite-Laufzeit unnötige Komplexität. |
| Eigenes Docker-Image bauen | `qgis/qgis` plus `pip install` im Job reicht. |
| `pb_tool` | Deployment-Helfer, kein Testwerkzeug. Lokal Geschmackssache. |
## 6. Bekannte Fallstricke
Wandern bei der Umsetzung nach `docs/wissen/fallstricke.md`:
- **`QT_QPA_PLATFORM` muss vor dem Erzeugen der `QgsApplication` gesetzt sein.** Danach wirkt es nicht mehr.
- **`qgis.utils.iface` ist nach `start_app()` `None`.** Entweder `pytest-qgis` verwenden oder selbst
`qgis.utils.iface = get_iface()` setzen — und darauf achten, keine Module zu früh zu importieren.
- **`QgsProject.instance()` ist global.** Ohne Aufräumen zwischen den Tests beeinflussen sie sich gegenseitig.
- **Abstürze beim Beenden** sind ein bekanntes QGIS-Thema, wenn Layer- oder Canvas-Objekte noch referenziert
werden. Gegenmittel: `pytest-qgis` oder `--qgis_disable_exit`.
- **`offscreen` hat keinen Fenstermanager.** Fokus, `activeWindow()` und Drag-and-drop verhalten sich anders.
Für Widget-Tests notfalls `xvfb-run -a` davorsetzen (nur Linux).
- **Modale Dialoge blockieren den Test.** Reihenfolge der Gegenmittel: den modalen Aufruf per `monkeypatch`
ersetzen; `open()` statt `exec()` verwenden; erst als letztes `QTimer.singleShot` mit einem Klick-Handler.
- **Locale.** Die Container laufen mit `LANG=C.UTF-8`, Entwickler-Windows meist auf Deutsch. Tests, die
formatierte Zahlen oder Datumsangaben vergleichen, brechen sonst nur an einem der beiden Orte.
- **QGIS 4 / PyQt6.** `exec_()` gibt es nicht mehr, Enum-Kurzformen (`Qt.UserRole`) auch nicht. Das trifft
Testcode genauso wie Produktivcode. Für den Umstieg gibt es das Prüfwerkzeug `pyqgis4-checker`.
## 7. Vorschlag zur Reihenfolge
1. **Entscheidung zu den Zielversionen** (Abschnitt 2) — blockiert Stufe 2, eigenes Issue.
2. **Stufe 1** an einem Plugin als Machbarkeitsnachweis, am besten `sn_basis` (keine Abhängigkeiten).
3. **Runner-Frage klären** — hat der Gitea-Runner Docker?
4. **Stufe 2** für `sn_basis`, dann für die Fachplugins.
5. **Stufe 3**, sobald mehr als eine Handvoll QGIS-Tests existiert.
6. **Stufe 4** als eigenes Vorhaben mit eigenem Konzept.
Die Stufen 1 bis 3 sind zusammen etwa der Aufwand, den heute ein größeres Fach-Issue kostet. Stufe 4 ist ein
eigenes Vorhaben.
## Quellen
Recherchestand 04.09.2026.
- `qgis.testing` (`start_app`, `QgisTestCase`): https://github.com/qgis/QGIS/blob/master/python/testing/__init__.py
- `qgis.testing.mocked.get_iface()`: https://github.com/qgis/QGIS/blob/master/python/testing/mocked.py
- Docker-Images und Test-Helfer: https://github.com/qgis/QGIS/blob/master/.docker/README.md
- Tag-Liste: https://hub.docker.com/r/qgis/qgis/tags
- Release-Fahrplan (LTR-Termine): https://qgis.org/resources/roadmap/
- Unit-Testing und Render-Checks: https://docs.qgis.org/testing/en/docs/developers_guide/unittesting.html
- `QgsRenderChecker` vs. `QgsMultiRenderChecker`: https://github.com/qgis/QGIS/blob/master/src/core/qgsrenderchecker.h
- `pytest-qgis`: https://github.com/GispoCoding/pytest-qgis · https://pypi.org/project/pytest-qgis/
- Modale Dialoge im Test: https://pytest-qt.readthedocs.io/en/latest/note_dialogs.html
- Migration zu Qt6/QGIS 4: https://plugins.qgis.org/docs/migrate-qgis4/ · https://github.com/qgis/pyqgis4-checker
- Gitea Actions, Runner-Labels: https://docs.gitea.com/usage/actions/act-runner/
Vorbilder mit echter QGIS-Testsuite, als Muster zum Abschauen:
- `3liz/lizmap-plugin` — Matrix über drei QGIS-Versionen, eigener `start_app()`-Ersatz, dem hiesigen Setup am nächsten
- `opengisch/qfieldsync` — Dialog-Tests gegen echtes QGIS, Matrix inklusive QGIS 4
- `opengisch/qgis_plugins_test_demo` — kleines Referenz-Repo, unter anderem ein Drucklayout-Test
+125
View File
@@ -0,0 +1,125 @@
# Repository „Projektwissen" auf Gitea einrichten
Prüfliste für den Maintainer. Stand 04.09.2026. Einmalig auszuführen; danach ist dieses Dokument nur noch
Nachweis, was wie eingestellt wurde.
Alles hier ist **Menschenarbeit**. Ein KI-Agent kann die Texte vorbereiten, aber weder Repositories anlegen noch
pushen noch Schutzregeln setzen (ADR 0007).
## 1. Repository anlegen
Gitea → Organisation `AG_QGIS` → **Neues Repository**.
| Einstellung | Wert |
|---|---|
| Name | `Projektwissen` |
| Beschreibung | Gemeinsame Wissensbasis, Regeln und Werkzeuge der QGIS-Plugin-Entwicklung LNO Sachsen |
| Sichtbarkeit | öffentlich (wie die übrigen Repos der Organisation) |
| `.gitignore`, Lizenz, README | **nicht** anlegen lassen — bringt der erste Push mit |
| Issues | an |
| Wiki | aus — die Doku liegt versioniert im Repo (ADR-Begründung in `docs/wissen/externe_dienste.md`) |
| Standard-Branch | wird beim ersten Push gesetzt |
## 2. Ersten Stand pushen
Der Ordner `AG_QGIS/` ist bereits als Git-Repository vorbereitet, mit einem Commit, der genau die Dateien
dieses Repositories enthält (die Plugin-Klone sind per `.gitignore` ausgenommen).
```bash
cd <…>/AG_QGIS
git status # erwartet: nichts Unversioniertes außer den Plugin-Klonen
git log --oneline # erwartet: der vorbereitete erste Commit
git remote add origin https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Projektwissen.git
git push -u origin unstable
```
Wir beginnen bewusst auf `unstable`, damit auch dieses Repository dem Kanal-Modell aus
[`../leitfaden/05_git_arbeitsweise.md`](../leitfaden/05_git_arbeitsweise.md) folgt.
**Vor dem Push prüfen:**
- [ ] **Autor des Commits stimmt.** `git log -1 --format='%an <%ae>'` – der vorbereitete Commit trägt die
Identität, die auf dem vorbereitenden Rechner in `git config --global` eingetragen war. Ist das nicht die
dienstliche Adresse, vor dem Push korrigieren:
```bash
git config user.name "Vorname Nachname"
git config user.email "dienstliche@adresse"
git commit --amend --reset-author --no-edit
```
- [ ] `git status` zeigt keine Plugin-Ordner (`Plugin_SN_*`, `Repository`, `Linkliste`, `Defaults`)
- [ ] `entwuerfe/` ist nicht enthalten
- [ ] keine Zugangsdaten, keine Tokens, keine echten Verfahrensdaten
- [ ] `python scripts/run_tests.py` läuft
- [ ] `python scripts/testkatalog.py --pruefen` meldet „aktuell"
## 3. Branches anlegen
```bash
git switch -c testing unstable && git push -u origin testing
git switch -c stable unstable && git push -u origin stable
git switch unstable
```
Danach in Gitea unter **Einstellungen → Allgemein** den Standard-Branch auf `unstable` setzen — dort landet die
tägliche Arbeit.
## 4. Branch-Schutz setzen
**Einstellungen → Branches** (englisch: *Settings → Branches*), dort eine neue Branch-Schutzregel anlegen — je
einmal für `stable`, `testing` und `unstable`:
| Einstellung | Wert |
|---|---|
| Branch-Name | `stable` / `testing` / `unstable` |
| Push deaktivieren | an |
| Merge-Freigabe erforderlich | mindestens 1 |
| Veraltete Freigaben verwerfen | an |
| Statusprüfungen erforderlich | erst aktivieren, wenn CI läuft (Abschnitt 6) |
## 5. Dasselbe für die Plugin-Repositories
Dort fehlt bisher der stabile Kanal. Je Repo (`Plugin_SN_Basis`, `Plugin_SN_Plan41`,
`Plugin_SN_Verfahrensgebiet`, `Plugin_SN_Widmung`, `Plugin_SN_FLT`, `Plugin_SN_Topografie`,
`Plugin_SN_Template`):
- [ ] `stable` anlegen — aus `testing`, wo es das gibt, sonst aus `unstable`. **Der Name muss exakt `stable`
lauten**, weil der Release-Workflow `git checkout "$CHANNEL"` ausführt (ADR 0007).
- [ ] Branch-Schutz für `stable`, `testing`, `unstable` wie in Abschnitt 4
- [ ] `oldstable` prüfen (`Plugin_SN_Basis`, `Plugin_SN_Plan41`): enthält er noch etwas Nötiges? Sonst löschen.
- [ ] Verwaiste Branches prüfen und löschen: `feature/Drucklayout-überarbeiten`, `print_layout` (Basis),
`bug/crash_on_exit` (Plan41), `alkis-nur-laden-stop`, `fix-fbschl-feldname` (Verfahrensgebiet)
Erst danach funktioniert ein Release auf dem stabilen Kanal (Tag ohne Suffix).
## 6. Optional: Tests in CI
Vorlage: [`../vorlagen/gitea_workflow_tests.yaml`](../vorlagen/gitea_workflow_tests.yaml) → als
`.gitea/workflows/tests.yaml` in `Plugin_SN_Basis`, `Plugin_SN_Plan41` und `Plugin_SN_Verfahrensgebiet`.
Sie klont das Projektwissen und die Nachbar-Repos und führt den Baseline-Lauf aus.
Voraussetzung: ein Gitea-Runner mit dem Label `alpine-latest` (wie beim Release-Workflow). Läuft das, in
Abschnitt 4 „Statusprüfungen erforderlich" aktivieren.
Der Ausbau zu Tests gegen echtes QGIS steht in
[`07_qgis_tests_plan.md`](07_qgis_tests_plan.md) und braucht einen Runner mit Docker.
## 7. Team informieren
- [ ] Link zum Repository und zur [`README.md`](../../README.md) verteilen
- [ ] Auf die Umstellung hinweisen: der Klon-Befehl heißt jetzt
`git clone .../Projektwissen.git AG_QGIS` (Ordnername unverändert)
- [ ] Um Durchsicht der Entscheidungen 0006–0009 bitten; nach Zustimmung den Status aller ADR von
„vorgeschlagen" auf „angenommen" setzen
- [ ] Jede Person trägt sich in `docs/status/STATUS.md` unter „Wer arbeitet woran" ein
## 8. Nach der Einrichtung zu klären
Diese Punkte liegen als Entwürfe bzw. offene Baustellen in `docs/status/STATUS.md`:
1. **QGIS-Mindestversion.** Steht auf 3.40, das seit Januar 2026 End of Life ist
([`07_qgis_tests_plan.md`](07_qgis_tests_plan.md) Abschnitt 2). Betrifft `workspace.toml` und alle
`.gitea/templates/metadata.template`.
2. **`.vscode/` aus den Plugin-Repos entfernen.** Liegt in vier Repos mit fest verdrahteten Pfaden eines
einzelnen Entwicklers. Je ein kleiner Pull Request.
3. **Release-Excludes ergänzen** (`Repository:hidden/workflows`): `AGENTS.md`, `CLAUDE.md`, `.agents/`,
`.claude/`, `docs/`, `tests/`, `.vscode/`.