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:
@@ -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.
|
||||
|
||||
|
||||
+15
-172
File diff suppressed because one or more lines are too long
+154
-153
@@ -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
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user