Prüfstand: Statistik-Kacheln je Paket, beabsichtigt/ungeklärt-Indikator

Jedes Paket zeigt jetzt dieselben Kennzahl-Kacheln wie die Gesamtübersicht
(Testdateien, Testfälle, grün/rot/übersprungen, mit Docstring, geprüft,
ohne Testbezug) statt eines Fließtexts.

Ungetestete Funktionen werden per Docstring-Auswertung als "beabsichtigt"
(Vermerk "von Test abgesehen, weil …" oder "Test ausgesetzt, zunächst
Refactor") oder "ungeklärt" markiert - in Markdown als Anmerkung je
Funktion, im Prüfstand als Pille mit Tooltip (Begründung).

Der Abschnitt "Ungetestete Funktionen" ist im Prüfstand jetzt eine
ausklappbare Karte wie eine Testdatei, mit eigener Kennzahl-Übersicht
(Anzahl, ungeklärt, beabsichtigt) im Kopf - bewusst von den Filter-Chips
ausgenommen (.gapcard), da diese sich auf Testfall-Status beziehen, nicht
auf Quellcode-Funktionen.

Neue Funktion absichtlicher_verzicht() plus 6 neue Tests in
scripts/tests/test_testkatalog_quellfunktionen.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 13:26:01 +02:00
co-authored by Claude Sonnet 5
parent cb29620f6e
commit cd11e409a3
5 changed files with 339 additions and 356 deletions
+8
View File
@@ -119,6 +119,14 @@ per `ast`, ohne Import). Das ist bewusst **keine** Zeilen-Coverage in Prozent
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.
File diff suppressed because one or more lines are too long
+154 -153
View File
@@ -32,7 +32,7 @@ Strukturelle Lücken (heuristisch, Namensabgleich): 149 von 413 (36 %) Funktione
- **übersprungen** – per `@unittest.skip`/`skipIf`/`skipUnless` abgeschaltet; der Grund steht dabei.
- Ein Testfall heißt `Testgruppe.test_name`; die Testgruppe fasst zusammengehörige Testfälle einer Datei zusammen.
- **geprüft** – Prüf-Audit (`scripts/test_audit.py`): **nie geprüft** kein Eintrag in `docs/status/testpruefungen.json`; **geprüft am …** ein Mensch hat den Testfall bestätigt und der Testcode hat sich seither nicht geändert; **veraltet seit Änderung …** der Testcode wurde nach der letzten Prüfung geändert.
- **ohne Testbezug** – Funktionen/Methoden im Quellcode, deren Name in keiner Testdatei des Pakets vorkommt (weder als Aufruf noch als `mock.patch("…")`-Ziel). Ein Namensabgleich, kein Ausführungsnachweis – siehe Abschnitt „Ungetestete Funktionen“ für die Grenzen dieser Heuristik. Das ist bewusst **keine** Zeilen-Coverage in Prozent (siehe ADR 0009, „Verworfene Alternativen“), sondern eine Lückenliste wie bei fehlenden Beschreibungen.
- **ohne Testbezug** – Funktionen/Methoden im Quellcode, deren Name in keiner Testdatei des Pakets vorkommt (weder als Aufruf noch als `mock.patch("…")`-Ziel). Ein Namensabgleich, kein Ausführungsnachweis – siehe Abschnitt „Ungetestete Funktionen“ für die Grenzen dieser Heuristik. Das ist bewusst **keine** Zeilen-Coverage in Prozent (siehe ADR 0009, „Verworfene Alternativen“), sondern eine Lückenliste wie bei fehlenden Beschreibungen. Davon **beabsichtigt** – der Docstring dokumentiert den Testverzicht; **ungeklärt** – weder Test noch Vermerk, die eigentliche Arbeitsliste.
## Paket `sn_basis`
@@ -1440,258 +1440,259 @@ Keine Lücken.
## Ungetestete Funktionen (strukturelle Lücke)
Heuristik, kein Beweis: Diese Funktionen und Methoden kommen unter ihrem Namen in keiner Testdatei des Pakets vor. Das schließt nicht aus, dass sie indirekt über eine andere, getestete Funktion mitlaufen – umgekehrt gilt: Taucht der Name irgendwo in einer Testdatei auf, gilt die Funktion schon als referenziert, auch bei zufälliger Namensgleichheit mit etwas anderem. `main.py`/`__init__.py` (QGIS-Einstiegspunkte wie `classFactory`, `initGui`, `unload`) und Dunder-Methoden sind ausgenommen. Diese Liste ersetzt keine fachliche Prüfung, ob ein Test sinnvoll wäre – dafür braucht es einen Menschen, der die Funktion kennt.
„beabsichtigt“ liest den Docstring der Funktion: Enthält er einen Vermerk wie „von Test abgesehen, weil …“ oder „Test ausgesetzt, zunächst Refactor“, gilt der fehlende Test als bewusste Entscheidung. Alles andere ist „ungeklärt“ – die eigentliche Arbeitsliste.
### `sn_basis` – 76 von 229 (33 %) ohne Testbezug
### `sn_basis` – 76 von 229 (33 %) ohne Testbezug (9 ungeklärt, 67 beabsichtigt)
#### `functions/dialog_wrapper.py` (9)
- `show_info_dialog` (Zeile 42)
- `ask_detailpruefung_oder_vg_laden` (Zeile 192)
- `ask_detailpruefung_oder_abbrechen` (Zeile 244)
- `ProgressDialog.set_total` (Zeile 339)
- `ProgressDialog.set_value` (Zeile 348)
- `ProgressDialog.set_label` (Zeile 361)
- `ProgressDialog.is_canceled` (Zeile 371)
- `ProgressDialog.raise_to_front` (Zeile 389)
- `create_progress_dialog` (Zeile 408)
- `show_info_dialog` (Zeile 42) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `ask_detailpruefung_oder_vg_laden` (Zeile 192) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `ask_detailpruefung_oder_abbrechen` (Zeile 244) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `ProgressDialog.set_total` (Zeile 339) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `ProgressDialog.set_value` (Zeile 348) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `ProgressDialog.set_label` (Zeile 361) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `ProgressDialog.is_canceled` (Zeile 371) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `ProgressDialog.raise_to_front` (Zeile 389) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `create_progress_dialog` (Zeile 408) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
#### `functions/message_wrapper.py` (2)
- `info` (Zeile 67)
- `success` (Zeile 84)
- `info` (Zeile 67) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `success` (Zeile 84) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
#### `functions/os_wrapper.py` (5)
- `normalize_path` (Zeile 43)
- `get_home_dir` (Zeile 55)
- `is_absolute_path` (Zeile 64)
- `basename` (Zeile 75)
- `path_suffix` (Zeile 106)
- `normalize_path` (Zeile 43) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `get_home_dir` (Zeile 55) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `is_absolute_path` (Zeile 64) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `basename` (Zeile 75) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `path_suffix` (Zeile 106) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
#### `functions/qgisui_wrapper.py` (1)
- `add_toolbar` (Zeile 204)
- `add_toolbar` (Zeile 204) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
#### `functions/qt_wrapper.py` (2)
- `exec_dialog` (Zeile 60)
- `debug_qt_status` (Zeile 67)
- `exec_dialog` (Zeile 60) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `debug_qt_status` (Zeile 67) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
#### `modules/DataGrabber.py` (3)
- `DataGrabber.set_source` (Zeile 68)
- `DataGrabber._process_database_source` (Zeile 312)
- `DataGrabber._process_service_source` (Zeile 324)
- `DataGrabber.set_source` (Zeile 68) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `DataGrabber._process_database_source` (Zeile 312) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `DataGrabber._process_service_source` (Zeile 324) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
#### `modules/Dateipruefer.py` (2)
- `Dateipruefer._ist_leer` (Zeile 98)
- `Dateipruefer._handle_leere_eingabe` (Zeile 227)
- `Dateipruefer._ist_leer` (Zeile 98) — ungeklärt
- `Dateipruefer._handle_leere_eingabe` (Zeile 227) — ungeklärt
#### `modules/Datenschreiber.py` (1)
- `Datenschreiber._store_style_in_gpkg` (Zeile 77)
- `Datenschreiber._store_style_in_gpkg` (Zeile 77) — ungeklärt
#### `modules/LayerLoader.py` (2)
- `LayerLoader._was_canceled` (Zeile 42)
- `LayerLoader._process_events` (Zeile 51)
- `LayerLoader._was_canceled` (Zeile 42) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `LayerLoader._process_events` (Zeile 51) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
#### `modules/linkpruefer.py` (3)
- `Linkpruefer._ist_url` (Zeile 41)
- `Linkpruefer._pruefe_url` (Zeile 81)
- `Linkpruefer._pruefe_dateipfad` (Zeile 115)
- `Linkpruefer._ist_url` (Zeile 41) — ungeklärt
- `Linkpruefer._pruefe_url` (Zeile 81) — ungeklärt
- `Linkpruefer._pruefe_dateipfad` (Zeile 115) — ungeklärt
#### `modules/print_layout.py` (11)
- `PrintLayout._create_map_item` (Zeile 98)
- `PrintLayout._set_item_frame_stroke_width` (Zeile 120)
- `PrintLayout._fit_extent_to_map_item` (Zeile 156)
- `PrintLayout.add_text_label` (Zeile 214)
- `PrintLayout._setup_layout_objects` (Zeile 246)
- `PrintLayout._create_label_item` (Zeile 378)
- `PrintLayout._resolve_visible_map_layers` (Zeile 476)
- `PrintLayout._apply_grid_common_settings` (Zeile 798)
- `PrintLayout._safe_grid_call` (Zeile 805)
- `PrintLayout._apply_grid_annotation_text_style` (Zeile 817)
- `PrintLayout._create_rectangle_box_item` (Zeile 918)
- `PrintLayout._create_map_item` (Zeile 98) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayout._set_item_frame_stroke_width` (Zeile 120) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayout._fit_extent_to_map_item` (Zeile 156) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayout.add_text_label` (Zeile 214) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayout._setup_layout_objects` (Zeile 246) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayout._create_label_item` (Zeile 378) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayout._resolve_visible_map_layers` (Zeile 476) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayout._apply_grid_common_settings` (Zeile 798) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayout._safe_grid_call` (Zeile 805) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayout._apply_grid_annotation_text_style` (Zeile 817) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayout._create_rectangle_box_item` (Zeile 918) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
#### `modules/print_layout_baufreigabe.py` (3)
- `PrintLayoutBaufreigabe.create_baufreigabe_layout` (Zeile 90)
- `PrintLayoutBaufreigabe._setup_layout_objects_baufreigabe` (Zeile 264)
- `PrintLayoutBaufreigabe._calc_atlas_margin_percent` (Zeile 398)
- `PrintLayoutBaufreigabe.create_baufreigabe_layout` (Zeile 90) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayoutBaufreigabe._setup_layout_objects_baufreigabe` (Zeile 264) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLayoutBaufreigabe._calc_atlas_margin_percent` (Zeile 398) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
#### `modules/print_logic.py` (20)
- `PrintLogic._failed_result` (Zeile 123)
- `PrintLogic._show_target_size_too_small` (Zeile 127)
- `PrintLogic._is_enabled_flag` (Zeile 135)
- `PrintLogic._resolve_layer_from_project` (Zeile 141)
- `PrintLogic._resolve_active_layer` (Zeile 162)
- `PrintLogic._get_druckpuffer_cm` (Zeile 172)
- `PrintLogic._get_druckpuffer_m` (Zeile 181)
- `PrintLogic._import_qgis_symbol` (Zeile 185)
- `PrintLogic._build_rectangle` (Zeile 193)
- `PrintLogic._clone_extent` (Zeile 203)
- `PrintLogic._get_existing_layer_by_name` (Zeile 270)
- `PrintLogic._create_druckrechteck_layer` (Zeile 284)
- `PrintLogic._clear_layer_features` (Zeile 313)
- `PrintLogic.configure_atlas_for_baufreigabe_layout` (Zeile 483)
- `PrintLogic._store_druckrechteck` (Zeile 631)
- `PrintLogic.activate_druckrechteck_capture` (Zeile 680)
- `PrintLogic._resolve_selection_layer` (Zeile 811)
- `PrintLogic._get_selected_extent` (Zeile 825)
- `PrintLogic._get_plot_dimensions_mm` (Zeile 1078)
- `PrintLogic._get_required_map_dimensions_mm` (Zeile 1088)
- `PrintLogic._failed_result` (Zeile 123) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._show_target_size_too_small` (Zeile 127) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._is_enabled_flag` (Zeile 135) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._resolve_layer_from_project` (Zeile 141) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._resolve_active_layer` (Zeile 162) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._get_druckpuffer_cm` (Zeile 172) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._get_druckpuffer_m` (Zeile 181) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._import_qgis_symbol` (Zeile 185) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._build_rectangle` (Zeile 193) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._clone_extent` (Zeile 203) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._get_existing_layer_by_name` (Zeile 270) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._create_druckrechteck_layer` (Zeile 284) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._clear_layer_features` (Zeile 313) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic.configure_atlas_for_baufreigabe_layout` (Zeile 483) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._store_druckrechteck` (Zeile 631) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic.activate_druckrechteck_capture` (Zeile 680) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._resolve_selection_layer` (Zeile 811) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._get_selected_extent` (Zeile 825) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._get_plot_dimensions_mm` (Zeile 1078) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._get_required_map_dimensions_mm` (Zeile 1088) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
#### `modules/Pruefmanager.py` (5)
- `Pruefmanager.report_summary` (Zeile 45)
- `Pruefmanager.zeige_hinweis` (Zeile 63)
- `Pruefmanager._handle_datei_existiert` (Zeile 87)
- `Pruefmanager._handle_verfahrensgebiet_layer_existiert` (Zeile 234)
- `Pruefmanager._ask_use_or_replace_pufferlayer` (Zeile 277)
- `Pruefmanager.report_summary` (Zeile 45) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `Pruefmanager.zeige_hinweis` (Zeile 63) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `Pruefmanager._handle_datei_existiert` (Zeile 87) — ungeklärt
- `Pruefmanager._handle_verfahrensgebiet_layer_existiert` (Zeile 234) — ungeklärt
- `Pruefmanager._ask_use_or_replace_pufferlayer` (Zeile 277) — ungeklärt
#### `ui/tabs/print_tab.py` (7)
- `PrintTab._build_ui` (Zeile 115)
- `PrintTab._on_kartenname_changed` (Zeile 335)
- `PrintTab._on_massstab_changed` (Zeile 349)
- `PrintTab._on_zielgroesse_changed` (Zeile 361)
- `PrintTab._on_formfaktor_changed` (Zeile 367)
- `PrintTab._on_druckbereich_changed` (Zeile 374)
- `PrintTab._format_druckpuffer_label` (Zeile 432)
- `PrintTab._build_ui` (Zeile 115) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `PrintTab._on_kartenname_changed` (Zeile 335) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `PrintTab._on_massstab_changed` (Zeile 349) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `PrintTab._on_zielgroesse_changed` (Zeile 361) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `PrintTab._on_formfaktor_changed` (Zeile 367) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `PrintTab._on_druckbereich_changed` (Zeile 374) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `PrintTab._format_druckpuffer_label` (Zeile 432) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
### `sn_verfahrensgebiet` – 34 von 104 (33 %) ohne Testbezug
### `sn_verfahrensgebiet` – 34 von 104 (33 %) ohne Testbezug (34 ungeklärt, 0 beabsichtigt)
#### `functions/beteiligung_pruefung.py` (5)
- `_baue_nenner_kennzeichen` (Zeile 139)
- `_hole_flurstuecke_layer` (Zeile 253)
- `_flurstueckskennzeichen_geometrien` (Zeile 258)
- `_try1_listenabgleich` (Zeile 279)
- `_lese_vef_vkzf` (Zeile 380)
- `_baue_nenner_kennzeichen` (Zeile 139) — ungeklärt
- `_hole_flurstuecke_layer` (Zeile 253) — ungeklärt
- `_flurstueckskennzeichen_geometrien` (Zeile 258) — ungeklärt
- `_try1_listenabgleich` (Zeile 279) — ungeklärt
- `_lese_vef_vkzf` (Zeile 380) — ungeklärt
#### `functions/flurstueck_alkis.py` (1)
- `_vorhandene_kennzeichen` (Zeile 80)
- `_vorhandene_kennzeichen` (Zeile 80) — ungeklärt
#### `functions/gemarkung_alkis.py` (1)
- `gemarkung_schluessel_von_vg_geometrie` (Zeile 212)
- `gemarkung_schluessel_von_vg_geometrie` (Zeile 212) — ungeklärt
#### `functions/grenzpunkt_alkis.py` (4)
- `_punktort_ta_bbox_param` (Zeile 115)
- `_hat_geometrie` (Zeile 573)
- `_lade_gemarkung_extents_mit_puffer` (Zeile 637)
- `_lade_punktort_ta_batches_per_gemarkung` (Zeile 784)
- `_punktort_ta_bbox_param` (Zeile 115) — ungeklärt
- `_hat_geometrie` (Zeile 573) — ungeklärt
- `_lade_gemarkung_extents_mit_puffer` (Zeile 637) — ungeklärt
- `_lade_punktort_ta_batches_per_gemarkung` (Zeile 784) — ungeklärt
#### `functions/knickpunkt_pruefung.py` (2)
- `_setze_kategorien_symbologie` (Zeile 158)
- `erstelle_korrigiertes_polygon` (Zeile 604)
- `_setze_kategorien_symbologie` (Zeile 158) — ungeklärt
- `erstelle_korrigiertes_polygon` (Zeile 604) — ungeklärt
#### `functions/umringspruefung.py` (3)
- `_zerlege_in_teile` (Zeile 55)
- `_setze_kategorien_symbologie` (Zeile 112)
- `umringspruefung` (Zeile 152)
- `_zerlege_in_teile` (Zeile 55) — ungeklärt
- `_setze_kategorien_symbologie` (Zeile 112) — ungeklärt
- `umringspruefung` (Zeile 152) — ungeklärt
#### `functions/umringstatistik_grenzpunkte.py` (3)
- `_setze_kategorien_symbologie` (Zeile 74)
- `_klassifiziere` (Zeile 119)
- `umringstatistik_grenzpunkte` (Zeile 164)
- `_setze_kategorien_symbologie` (Zeile 74) — ungeklärt
- `_klassifiziere` (Zeile 119) — ungeklärt
- `umringstatistik_grenzpunkte` (Zeile 164) — ungeklärt
#### `functions/verfahrensgebiet_alkis.py` (1)
- `_baue_layer_aus_kombinationen` (Zeile 144)
- `_baue_layer_aus_kombinationen` (Zeile 144) — ungeklärt
#### `functions/verfahrensgebiet_alkis_komplett.py` (2)
- `_entferne_hilfs_layer` (Zeile 100)
- `_flaeche_des_layers` (Zeile 210)
- `_entferne_hilfs_layer` (Zeile 100) — ungeklärt
- `_flaeche_des_layers` (Zeile 210) — ungeklärt
#### `functions/verfahrensgebiet_dialoge.py` (1)
- `ask_landkreis_objekte` (Zeile 101)
- `ask_landkreis_objekte` (Zeile 101) — ungeklärt
#### `functions/verfahrensgebiet_shape_komplett.py` (6)
- `_normalisiere_feldname` (Zeile 110)
- `_finde_fbschl_index` (Zeile 132)
- `_ermittle_gebietstyp_aus_fbschl` (Zeile 161)
- `_wende_vergleich_stil_an` (Zeile 211)
- `_erstelle_vergleich_layer` (Zeile 251)
- `_finde_alle_shp` (Zeile 342)
- `_normalisiere_feldname` (Zeile 110) — ungeklärt
- `_finde_fbschl_index` (Zeile 132) — ungeklärt
- `_ermittle_gebietstyp_aus_fbschl` (Zeile 161) — ungeklärt
- `_wende_vergleich_stil_an` (Zeile 211) — ungeklärt
- `_erstelle_vergleich_layer` (Zeile 251) — ungeklärt
- `_finde_alle_shp` (Zeile 342) — ungeklärt
#### `ui/tabs/working_tab.py` (5)
- `WorkingTab.handle_beteiligung_pruefen` (Zeile 297)
- `WorkingTab._haken_label` (Zeile 349)
- `WorkingTab._restore_verf_layer_selection` (Zeile 374)
- `WorkingTab._uebernimm_flurstuecke_ergebnis` (Zeile 390)
- `WorkingTab.handle_umringstatistik` (Zeile 777)
- `WorkingTab.handle_beteiligung_pruefen` (Zeile 297) — ungeklärt
- `WorkingTab._haken_label` (Zeile 349) — ungeklärt
- `WorkingTab._restore_verf_layer_selection` (Zeile 374) — ungeklärt
- `WorkingTab._uebernimm_flurstuecke_ergebnis` (Zeile 390) — ungeklärt
- `WorkingTab.handle_umringstatistik` (Zeile 777) — ungeklärt
### `sn_plan41` – 39 von 80 (49 %) ohne Testbezug
### `sn_plan41` – 39 von 80 (49 %) ohne Testbezug (39 ungeklärt, 0 beabsichtigt)
#### `modules/vln_api_client.py` (3)
- `KartenApiClient.load_feature_collection` (Zeile 154)
- `KartenApiClient._request` (Zeile 176)
- `KartenApiClient._error_detail` (Zeile 235)
- `KartenApiClient.load_feature_collection` (Zeile 154) — ungeklärt
- `KartenApiClient._request` (Zeile 176) — ungeklärt
- `KartenApiClient._error_detail` (Zeile 235) — ungeklärt
#### `modules/vln_api_logic.py` (4)
- `VlnApiLogic.get_p41_layers` (Zeile 233)
- `VlnApiLogic.remove_p41_layers` (Zeile 237)
- `VlnApiLogic._apply_p41_styles` (Zeile 277)
- `VlnApiLogic.upload_summary` (Zeile 347)
- `VlnApiLogic.get_p41_layers` (Zeile 233) — ungeklärt
- `VlnApiLogic.remove_p41_layers` (Zeile 237) — ungeklärt
- `VlnApiLogic._apply_p41_styles` (Zeile 277) — ungeklärt
- `VlnApiLogic.upload_summary` (Zeile 347) — ungeklärt
#### `modules/vln_layer_manager.py` (2)
- `_family` (Zeile 58)
- `memory_layer_from_features` (Zeile 73)
- `_family` (Zeile 58) — ungeklärt
- `memory_layer_from_features` (Zeile 73) — ungeklärt
#### `ui/dockwidget.py` (1)
- `DockWidget._init_tabs_with_services` (Zeile 42)
- `DockWidget._init_tabs_with_services` (Zeile 42) — ungeklärt
#### `ui/tab_a_logic.py` (15)
- `TabALogic._log` (Zeile 90)
- `TabALogic._create_silent_pruefmanager` (Zeile 93)
- `TabALogic._zeige_verfahrensgebiet_hinweis` (Zeile 127)
- `TabALogic._close_progress_dialog` (Zeile 139)
- `TabALogic.write_layer_to_verfahrens_db` (Zeile 169)
- `TabALogic.is_valid_verfahrensgebiet_layer` (Zeile 242)
- `TabALogic._load_dienste_aus_result_dict` (Zeile 391)
- `TabALogic._create_local_layer_from_fetched_features` (Zeile 639)
- `TabALogic._write_html_log` (Zeile 779)
- `TabALogic._clone_layer_with_extent` (Zeile 934)
- `TabALogic._handle_pufferlayer` (Zeile 1062)
- `TabALogic._load_existing_pufferlayer` (Zeile 1084)
- `TabALogic._create_pufferlayer` (Zeile 1092)
- `TabALogic._pufferlayer_exists_in_source` (Zeile 1119)
- `TabALogic.Pufferlayer_erstellen` (Zeile 1133)
- `TabALogic._log` (Zeile 90) — ungeklärt
- `TabALogic._create_silent_pruefmanager` (Zeile 93) — ungeklärt
- `TabALogic._zeige_verfahrensgebiet_hinweis` (Zeile 127) — ungeklärt
- `TabALogic._close_progress_dialog` (Zeile 139) — ungeklärt
- `TabALogic.write_layer_to_verfahrens_db` (Zeile 169) — ungeklärt
- `TabALogic.is_valid_verfahrensgebiet_layer` (Zeile 242) — ungeklärt
- `TabALogic._load_dienste_aus_result_dict` (Zeile 391) — ungeklärt
- `TabALogic._create_local_layer_from_fetched_features` (Zeile 639) — ungeklärt
- `TabALogic._write_html_log` (Zeile 779) — ungeklärt
- `TabALogic._clone_layer_with_extent` (Zeile 934) — ungeklärt
- `TabALogic._handle_pufferlayer` (Zeile 1062) — ungeklärt
- `TabALogic._load_existing_pufferlayer` (Zeile 1084) — ungeklärt
- `TabALogic._create_pufferlayer` (Zeile 1092) — ungeklärt
- `TabALogic._pufferlayer_exists_in_source` (Zeile 1119) — ungeklärt
- `TabALogic.Pufferlayer_erstellen` (Zeile 1133) — ungeklärt
#### `ui/tab_a_ui.py` (14)
- `TabA._build_ui` (Zeile 113)
- `TabA._restore_state` (Zeile 274)
- `TabA._restore_vln_state` (Zeile 296)
- `TabA._toggle_vln_group` (Zeile 307)
- `TabA._update_vln_ui_state` (Zeile 311)
- `TabA._populate_verfahren_combo` (Zeile 347)
- `TabA._on_vln_login` (Zeile 374)
- `TabA._on_vln_logout` (Zeile 405)
- `TabA._on_vln_verfahren_changed` (Zeile 416)
- `TabA._on_p41_laden` (Zeile 424)
- `TabA._on_upload_active` (Zeile 498)
- `TabA._create_new_gpkg` (Zeile 595)
- `TabA._update_group_color` (Zeile 608)
- `TabA._on_load_fachdaten` (Zeile 618)
- `TabA._build_ui` (Zeile 113) — ungeklärt
- `TabA._restore_state` (Zeile 274) — ungeklärt
- `TabA._restore_vln_state` (Zeile 296) — ungeklärt
- `TabA._toggle_vln_group` (Zeile 307) — ungeklärt
- `TabA._update_vln_ui_state` (Zeile 311) — ungeklärt
- `TabA._populate_verfahren_combo` (Zeile 347) — ungeklärt
- `TabA._on_vln_login` (Zeile 374) — ungeklärt
- `TabA._on_vln_logout` (Zeile 405) — ungeklärt
- `TabA._on_vln_verfahren_changed` (Zeile 416) — ungeklärt
- `TabA._on_p41_laden` (Zeile 424) — ungeklärt
- `TabA._on_upload_active` (Zeile 498) — ungeklärt
- `TabA._create_new_gpkg` (Zeile 595) — ungeklärt
- `TabA._update_group_color` (Zeile 608) — ungeklärt
- `TabA._on_load_fachdaten` (Zeile 618) — ungeklärt
+120 -25
View File
@@ -136,6 +136,7 @@ class Quellfunktion:
name: str
zeile: int
referenziert: bool = False
absichtlich_grund: str = ""
@dataclass
@@ -304,6 +305,26 @@ def _ist_dunder(name: str) -> bool:
return name.startswith("__") and name.endswith("__")
ABSICHTLICH_MARKER = ("von Test abgesehen", "Test ausgesetzt")
def absichtlicher_verzicht(docstring: str | None) -> str:
"""Docstring-Zeile, die einen bewusst dokumentierten Testverzicht enthält, sonst "".
Erkennt die beiden im Projekt verwendeten Vermerke: "von Test abgesehen, weil …" (bewusst
vernachlässigbar) und "Test ausgesetzt, zunächst Refactor" (zurückgestellt bis zu einer geplanten
Überarbeitung). Ein Namensabgleich wie bei `sammle_referenzierte_namen` – reicht der Vermerk nicht
wörtlich, wird die Funktion weiterhin als offen (ungeklärt) geführt.
"""
if not docstring:
return ""
for zeile in docstring.splitlines():
text = " ".join(zeile.split())
if text and any(marker in text for marker in ABSICHTLICH_MARKER):
return text
return ""
def sammle_quellcode(ordner: Path) -> list[Quellfunktion]:
"""Listet Top-Level-Funktionen und Methoden im Quellcode eines Pakets (ohne tests/).
@@ -323,12 +344,24 @@ def sammle_quellcode(ordner: Path) -> list[Quellfunktion]:
baum = ast.parse(pfad.read_text(encoding="utf-8"), filename=str(pfad))
for knoten in baum.body:
if isinstance(knoten, (ast.FunctionDef, ast.AsyncFunctionDef)):
ergebnisse.append(Quellfunktion(datei=anzeigepfad, name=knoten.name, zeile=knoten.lineno))
ergebnisse.append(
Quellfunktion(
datei=anzeigepfad,
name=knoten.name,
zeile=knoten.lineno,
absichtlich_grund=absichtlicher_verzicht(ast.get_docstring(knoten)),
)
)
elif isinstance(knoten, ast.ClassDef):
for glied in knoten.body:
if isinstance(glied, (ast.FunctionDef, ast.AsyncFunctionDef)) and not _ist_dunder(glied.name):
ergebnisse.append(
Quellfunktion(datei=anzeigepfad, name=f"{knoten.name}.{glied.name}", zeile=glied.lineno)
Quellfunktion(
datei=anzeigepfad,
name=f"{knoten.name}.{glied.name}",
zeile=glied.lineno,
absichtlich_grund=absichtlicher_verzicht(ast.get_docstring(glied)),
)
)
return ergebnisse
@@ -364,10 +397,13 @@ def setze_referenzstatus(katalog: Paketkatalog) -> None:
def zaehle_quellfunktionen(funktionen: list[Quellfunktion]) -> dict[str, int]:
ohne_bezug = [f for f in funktionen if not f.referenziert]
return {
"gesamt": len(funktionen),
"referenziert": sum(1 for f in funktionen if f.referenziert),
"ohne_bezug": sum(1 for f in funktionen if not f.referenziert),
"ohne_bezug": len(ohne_bezug),
"absichtlich": sum(1 for f in ohne_bezug if f.absichtlich_grund),
"ungeklaert": sum(1 for f in ohne_bezug if not f.absichtlich_grund),
}
@@ -565,7 +601,8 @@ def erzeuge_markdown(
"vorkommt (weder als Aufruf noch als `mock.patch(\"…\")`-Ziel). Ein Namensabgleich, kein Ausführungsnachweis "
"– siehe Abschnitt „Ungetestete Funktionen“ für die Grenzen dieser Heuristik. Das ist bewusst **keine** "
"Zeilen-Coverage in Prozent (siehe ADR 0009, „Verworfene Alternativen“), sondern eine Lückenliste wie bei "
"fehlenden Beschreibungen."
"fehlenden Beschreibungen. Davon **beabsichtigt** – der Docstring dokumentiert den Testverzicht; "
"**ungeklärt** – weder Test noch Vermerk, die eigentliche Arbeitsliste."
)
z.append("")
@@ -643,10 +680,18 @@ def erzeuge_markdown(
" Diese Liste ersetzt keine fachliche Prüfung, ob ein Test sinnvoll wäre – dafür braucht es einen"
" Menschen, der die Funktion kennt."
)
z.append(
"„beabsichtigt“ liest den Docstring der Funktion: Enthält er einen Vermerk wie „von Test abgesehen,"
" weil …“ oder „Test ausgesetzt, zunächst Refactor“, gilt der fehlende Test als bewusste Entscheidung."
" Alles andere ist „ungeklärt“ – die eigentliche Arbeitsliste."
)
z.append("")
for katalog in kataloge:
qf = zaehle_quellfunktionen(katalog.quellfunktionen)
z.append(f"### `{katalog.paket}` – {anteil(qf['ohne_bezug'], qf['gesamt'])} ohne Testbezug")
z.append(
f"### `{katalog.paket}` – {anteil(qf['ohne_bezug'], qf['gesamt'])} ohne Testbezug "
f"({qf['ungeklaert']} ungeklärt, {qf['absichtlich']} beabsichtigt)"
)
z.append("")
luecken_qf = [f for f in katalog.quellfunktionen if not f.referenziert]
if not luecken_qf:
@@ -660,7 +705,10 @@ def erzeuge_markdown(
z.append(f"#### `{datei}` ({len(funktionen)})")
z.append("")
for funktion in funktionen:
z.append(f"- `{funktion.name}` (Zeile {funktion.zeile})")
if funktion.absichtlich_grund:
z.append(f"- `{funktion.name}` (Zeile {funktion.zeile}) — **beabsichtigt:** {zelle(funktion.absichtlich_grund)}")
else:
z.append(f"- `{funktion.name}` (Zeile {funktion.zeile}) — ungeklärt")
z.append("")
return "\n".join(z) + "\n"
@@ -796,51 +844,94 @@ def _html_karte_fuer_datei(datei: Testdatei) -> str:
def _html_luecken_fuer_paket(katalog: Paketkatalog) -> str:
"""Statischer Block je Paket: Quellfunktionen ohne erkennbaren Testbezug."""
"""Ausklappbare Karte je Paket (wie eine Testdatei-Karte): Quellfunktionen ohne erkennbaren Testbezug."""
qf = zaehle_quellfunktionen(katalog.quellfunktionen)
if not qf["gesamt"]:
return ""
luecken = [f for f in katalog.quellfunktionen if not f.referenziert]
fid = _slug(f"{katalog.paket}-luecken")
luecken = sorted([f for f in katalog.quellfunktionen if not f.referenziert], key=lambda f: (f.datei, f.zeile))
if not luecken:
koerper = '<p class="empty-note">Keine Lücken.</p>'
tabelle = '<p class="empty-note">Keine Lücken.</p>'
else:
zeilen = "\n".join(
zeilen = []
for f in luecken:
if f.absichtlich_grund:
status = f'<span class="pill pill-skip" title="{html.escape(f.absichtlich_grund)}">beabsichtigt</span>'
else:
status = '<span class="pill pill-warn">ungeklärt</span>'
zeilen.append(
f'<tr><td class="mono">{html.escape(f.datei)}</td><td class="mono">{html.escape(f.name)}</td>'
f"<td>{f.zeile}</td></tr>"
for f in luecken
f"<td>{f.zeile}</td><td>{status}</td></tr>"
)
koerper = (
'<table class="test-table"><thead><tr><th>Datei</th><th>Funktion/Methode</th><th>Zeile</th></tr></thead>'
f"<tbody>{zeilen}</tbody></table>"
tabelle = (
'<table class="test-table"><thead><tr><th>Datei</th><th>Funktion/Methode</th><th>Zeile</th>'
"<th>Status</th></tr></thead>"
f"<tbody>{''.join(zeilen)}</tbody></table>"
)
beschreibung = (
'<p class="filedesc">Heuristik: Name kommt in keiner Testdatei des Pakets vor (Details siehe '
'<span class="mono">docs/tests/testkatalog.md</span>). <b>beabsichtigt</b> liest den Docstring der '
"Funktion – ein Vermerk wie „von Test abgesehen, weil …“ oder „Test ausgesetzt, zunächst Refactor“ "
"gilt als bewusste Entscheidung. <b>ungeklärt</b> hat weder Test noch Vermerk.</p>"
)
suchtext = html.escape(f"ungetestete funktionen {katalog.paket}".lower())
header = (
f'<button class="card-toggle" data-target="{fid}" aria-expanded="false">'
'<span class="card-path mono">Ungetestete Funktionen</span>'
'<span class="card-meta">'
f'<span class="count-total">{qf["gesamt"]} Funktionen</span>'
f'<span class="cpill cpill-warn">{qf["ungeklaert"]} ungeklärt</span>'
f'<span class="cpill cpill-skip">{qf["absichtlich"]} beabsichtigt</span>'
'<span class="chev" aria-hidden="true">▾</span></span></button>'
)
return (
f'<article class="filecard gapcard" data-search="{suchtext}">'
f"{header}"
f'<div class="card-body" id="body-{fid}">{beschreibung}{tabelle}</div>'
"</article>"
)
return f"""
<div class="luecken-block" style="margin-top:18px">
<h3 style="font-size:15px">Ungetestete Funktionen (strukturell) – {qf['ohne_bezug']} von {qf['gesamt']}</h3>
<p class="empty-note">Heuristik: Name kommt in keiner Testdatei des Pakets vor. Details siehe docs/tests/testkatalog.md.</p>
{koerper}
</div>
"""
def _html_abschnitt_fuer_paket(katalog: Paketkatalog) -> str:
k = zaehle(katalog.faelle)
qf = zaehle_quellfunktionen(katalog.quellfunktionen)
rolle = ROLLENTEXT.get(katalog.rolle, katalog.rolle or "Plugin")
karten = "".join(_html_karte_fuer_datei(d) for d in katalog.dateien) or "<p>Keine Testdateien gefunden.</p>"
gesamt = k["gesamt"] or 1
ok_pct = round(100 * k["gruen"] / gesamt)
warn_pct = round(100 * k["rot"] / gesamt)
skip_pct = max(0, 100 - ok_pct - warn_pct)
doc_pct = round(100 * k["mit_docstring"] / k["gesamt"]) if k["gesamt"] else 0
audit_pct = round(100 * k["aktuell_geprueft"] / k["gesamt"]) if k["gesamt"] else 0
kacheln = [
("", len(katalog.dateien), "Testdateien"),
("", k["gesamt"], "Testfälle"),
("ok", k["gruen"], "grün"),
("warn", k["rot"], "rot (Baseline)"),
("skip", k["uebersprungen"], "übersprungen"),
("", f"{doc_pct}%", "mit Docstring"),
("", f"{audit_pct}%", "geprüft"),
("warn" if qf["ungeklaert"] else "", qf["ohne_bezug"], "ohne Testbezug"),
]
tiles = "".join(
f'<div class="stat stat-sm{" " + klasse if klasse else ""}"><span class="n">{wert}</span>'
f'<span class="l">{label}</span></div>'
for klasse, wert, label in kacheln
)
return f"""
<section class="pkg-section" id="pkg-{html.escape(katalog.paket)}">
<div class="pkg-head">
<div>
<h2>{html.escape(katalog.paket)}</h2>
<p class="pkg-sub">{html.escape(rolle)} · Ordner <span class="mono">{html.escape(katalog.ordner)}</span> · {len(katalog.dateien)} Testdateien · {k['gesamt']} Testfälle</p>
<p class="pkg-sub">{html.escape(rolle)} · Ordner <span class="mono">{html.escape(katalog.ordner)}</span></p>
</div>
<div class="pkg-bar" role="img" aria-label="{k['gruen']} grün, {k['rot']} rot, {k['uebersprungen']} übersprungen">
<span class="seg seg-ok" style="width:{ok_pct}%"></span><span class="seg seg-warn" style="width:{warn_pct}%"></span><span class="seg seg-skip" style="width:{skip_pct}%"></span>
</div>
</div>
<div class="pkg-summary">{tiles}</div>
<div class="card-grid">{karten}</div>
{_html_luecken_fuer_paket(katalog)}
</section>
@@ -924,6 +1015,10 @@ _HTML_KOPF = """<title>Prüfstand</title>
.pkg-bar{ width:220px; height:9px; border-radius:6px; overflow:hidden; display:flex; background:var(--surface-2); border:1px solid var(--line); }
.pkg-bar .seg{ height:100%; display:block; }
.seg-ok{ background:var(--ok); } .seg-warn{ background:var(--warn); } .seg-skip{ background:var(--skip); }
.pkg-summary{ max-width:100%; margin:0 0 16px; display:grid; gap:8px; grid-template-columns:repeat(auto-fit,minmax(108px,1fr)); }
.stat-sm{ padding:8px 10px; }
.stat-sm .n{ font-size:17px; }
.stat-sm .l{ font-size:10px; }
.card-grid{ display:grid; gap:12px; }
.filecard{ background:var(--surface); border:1px solid var(--line); border-radius:12px; box-shadow:var(--shadow); overflow:hidden; }
.card-toggle{ width:100%; display:flex; flex-wrap:wrap; align-items:center; gap:8px 14px; justify-content:space-between;
@@ -1000,13 +1095,13 @@ _HTML_SKRIPT = """
function applyFilters(){
var active = document.querySelector('.chip[aria-pressed="true"]').dataset.filter;
var q = document.getElementById('searchInput').value.trim().toLowerCase();
document.querySelectorAll('.filecard').forEach(function(card){
document.querySelectorAll('.filecard:not(.gapcard)').forEach(function(card){
var passFilter = active === 'all' || card.dataset[active] === '1';
var passSearch = !q || card.dataset.search.indexOf(q) !== -1;
card.hidden = !(passFilter && passSearch);
});
document.querySelectorAll('.pkg-section').forEach(function(sec){
sec.hidden = !sec.querySelector('.filecard:not([hidden])');
sec.hidden = !sec.querySelector('.filecard:not(.gapcard):not([hidden])');
});
}
document.querySelectorAll('.chip').forEach(function(chip){
@@ -38,6 +38,10 @@ class Beispielklasse:
def ungetestete_methode(self):
return self.wert
def bewusst_ausgelassene_methode(self):
"""von Test abgesehen, weil vernachlässigbar; XY 01.01.2026"""
return self.wert
'''
TESTDATEI_MIT_AUFRUF = '''"""Testet Beispielklasse."""
@@ -96,6 +100,35 @@ class SammleQuellcodeTests(unittest.TestCase):
namen = {f.name for f in tk.sammle_quellcode(self.ordner)}
self.assertNotIn("Beispielklasse.__init__", namen)
def test_docstring_mit_vermerk_setzt_absichtlich_grund(self):
"""Prüft, dass eine Methode mit „von Test abgesehen“-Vermerk im Docstring absichtlich_grund gesetzt bekommt."""
funktionen = {f.name: f for f in tk.sammle_quellcode(self.ordner)}
self.assertIn("von Test abgesehen", funktionen["Beispielklasse.bewusst_ausgelassene_methode"].absichtlich_grund)
def test_docstring_ohne_vermerk_laesst_absichtlich_grund_leer(self):
"""Prüft, dass eine Methode ohne Vermerk im Docstring ein leeres absichtlich_grund behält."""
funktionen = {f.name: f for f in tk.sammle_quellcode(self.ordner)}
self.assertEqual(funktionen["Beispielklasse.ungetestete_methode"].absichtlich_grund, "")
class AbsichtlicherVerzichtTests(unittest.TestCase):
def test_erkennt_von_test_abgesehen_marker(self):
"""Prüft, dass absichtlicher_verzicht den 'von Test abgesehen'-Vermerk erkennt."""
text = tk.absichtlicher_verzicht("Kurzbeschreibung.\n\nvon Test abgesehen, weil vernachlässigbar; XY 01.01.2026")
self.assertIn("von Test abgesehen", text)
def test_erkennt_test_ausgesetzt_marker(self):
"""Prüft, dass absichtlicher_verzicht den 'Test ausgesetzt'-Vermerk erkennt."""
self.assertEqual(tk.absichtlicher_verzicht("Test ausgesetzt, zunächst Refactor"), "Test ausgesetzt, zunächst Refactor")
def test_liefert_leeren_string_ohne_marker(self):
"""Prüft, dass absichtlicher_verzicht ohne bekannten Vermerk einen leeren String liefert."""
self.assertEqual(tk.absichtlicher_verzicht("Ganz normale Beschreibung ohne Vermerk."), "")
def test_liefert_leeren_string_fuer_fehlenden_docstring(self):
"""Prüft, dass absichtlicher_verzicht für None einen leeren String liefert."""
self.assertEqual(tk.absichtlicher_verzicht(None), "")
class SammleReferenzierteNamenTests(unittest.TestCase):
def test_erkennt_aufruf_attribut_und_string_ziel(self):
@@ -131,15 +164,18 @@ class SetzeReferenzstatusTests(unittest.TestCase):
class ZaehleQuellfunktionenTests(unittest.TestCase):
def test_zaehlt_referenzierte_und_ungetestete_funktionen_korrekt(self):
"""Prüft, dass zaehle_quellfunktionen Gesamtzahl, referenziert und ohne_bezug korrekt zählt."""
def test_zaehlt_referenzierte_beabsichtigte_und_ungeklaerte_funktionen_korrekt(self):
"""Prüft, dass zaehle_quellfunktionen zwischen referenziert, beabsichtigt und ungeklärt unterscheidet."""
funktionen = [
tk.Quellfunktion(datei="modul.py", name="a", zeile=1, referenziert=True),
tk.Quellfunktion(datei="modul.py", name="b", zeile=2, referenziert=False),
tk.Quellfunktion(datei="modul.py", name="b", zeile=2, referenziert=False, absichtlich_grund="von Test abgesehen, weil x"),
tk.Quellfunktion(datei="modul.py", name="c", zeile=3, referenziert=False),
]
ergebnis = tk.zaehle_quellfunktionen(funktionen)
self.assertEqual(ergebnis, {"gesamt": 3, "referenziert": 1, "ohne_bezug": 2})
self.assertEqual(
ergebnis,
{"gesamt": 3, "referenziert": 1, "ohne_bezug": 2, "absichtlich": 1, "ungeklaert": 1},
)
if __name__ == "__main__":