Files
AndreasTharangandClaude Sonnet 5 ab416fb34c 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>
2026-09-04 11:57:26 +02:00

245 lines
13 KiB
Markdown
Raw Permalink 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.
# 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