Verfahrensgebietsprüfung auf Knickpunkte umgestellt
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
"""sn_verfahrensgebiet/tests/test_knickpunkt_pruefung.py
|
||||
|
||||
Unit-Tests für :mod:`sn_verfahrensgebiet.functions.knickpunkt_pruefung`.
|
||||
|
||||
Getestete Szenarien:
|
||||
|
||||
_extrahiere_knickpunkte:
|
||||
- Leerer Layer → leere Liste
|
||||
- Polygon mit 4 Knickpunkten → korrekte Anzahl (ohne doppelten Schlusspunkt)
|
||||
- Keine Geometrie → leere Liste
|
||||
|
||||
knickpunkt_pruefung:
|
||||
- Leere Knickpunktliste → (None, 0, 0, 0)
|
||||
- Kein PunktortTA-Layer → (None, 0, 0, 0)
|
||||
- Identischer Punkt → grün gezählt
|
||||
- Kein passender PunktortTA → rot gezählt
|
||||
- PunktortTA nahe Umring auf Flurstücksgrenze → orange gezählt
|
||||
- Abbruch via progress.is_canceled() → (None, 0, 0, 0)
|
||||
"""
|
||||
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Minimales QGIS-Mock-Setup (identisch mit test_verfahrensgebiet_alkis_komplett)
|
||||
# ---------------------------------------------------------------------------
|
||||
if "qgis" not in sys.modules:
|
||||
qgis_mod = types.ModuleType("qgis")
|
||||
qgis_core = types.ModuleType("qgis.core")
|
||||
# Attribute vorab setzen, damit patch() sie überschreiben kann
|
||||
qgis_core.QgsPointXY = None
|
||||
qgis_core.QgsRectangle = None
|
||||
qgis_core.QgsSpatialIndex = None
|
||||
qgis_core.QgsMarkerSymbol = None
|
||||
qgis_core.QgsCategorizedSymbolRenderer = None
|
||||
qgis_core.QgsRendererCategory = None
|
||||
qgis_utils = types.ModuleType("qgis.utils")
|
||||
qgis_utils.iface = types.SimpleNamespace()
|
||||
qgis_utils.plugins = {}
|
||||
qgis_mod.utils = qgis_utils
|
||||
qgis_mod.core = qgis_core
|
||||
sys.modules["qgis"] = qgis_mod
|
||||
sys.modules["qgis.core"] = qgis_core
|
||||
sys.modules["qgis.utils"] = qgis_utils
|
||||
|
||||
from sn_verfahrensgebiet.functions.knickpunkt_pruefung import (
|
||||
knickpunkt_pruefung,
|
||||
_extrahiere_knickpunkte,
|
||||
WERT_IDENTISCH,
|
||||
WERT_KEIN_PTA,
|
||||
WERT_FEHLENDER_KNICK,
|
||||
)
|
||||
|
||||
_MOD = "sn_verfahrensgebiet.functions.knickpunkt_pruefung"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hilfsfunktionen
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _make_layer(valid=True, crs="EPSG:25833", lid="L1"):
|
||||
layer = MagicMock()
|
||||
layer.isValid.return_value = valid
|
||||
layer.id.return_value = lid
|
||||
mock_crs = MagicMock()
|
||||
mock_crs.authid.return_value = crs
|
||||
layer.crs.return_value = mock_crs
|
||||
layer.getFeatures.return_value = []
|
||||
return layer
|
||||
|
||||
|
||||
def _make_progress(canceled_after: int = 9999):
|
||||
"""Progress-Mock: gibt is_canceled() nach N Aufrufen True zurück."""
|
||||
call_count = [0]
|
||||
|
||||
def _is_canceled():
|
||||
call_count[0] += 1
|
||||
return call_count[0] > canceled_after
|
||||
|
||||
prog = MagicMock()
|
||||
prog.is_canceled.side_effect = _is_canceled
|
||||
return prog
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: _extrahiere_knickpunkte
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestExtrahiereKnickpunkte(unittest.TestCase):
|
||||
|
||||
def test_leerer_layer_gibt_leere_liste(self):
|
||||
layer = _make_layer()
|
||||
layer.getFeatures.return_value = []
|
||||
result = _extrahiere_knickpunkte(layer)
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_keine_gueltige_geometrie_gibt_leere_liste(self):
|
||||
feat = MagicMock()
|
||||
feat.geometry.return_value = None
|
||||
layer = _make_layer()
|
||||
layer.getFeatures.return_value = [feat]
|
||||
result = _extrahiere_knickpunkte(layer)
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_feature_mit_leerer_geometrie_gibt_leere_liste(self):
|
||||
feat = MagicMock()
|
||||
geom = MagicMock()
|
||||
geom.isEmpty.return_value = True
|
||||
feat.geometry.return_value = geom
|
||||
layer = _make_layer()
|
||||
layer.getFeatures.return_value = [feat]
|
||||
result = _extrahiere_knickpunkte(layer)
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_qgis_nicht_verfuegbar_gibt_leere_liste(self):
|
||||
"""Wenn qgis.core kein QgsPointXY hat (ImportError in Funktion), leere Liste."""
|
||||
layer = _make_layer()
|
||||
feat = MagicMock()
|
||||
geom = MagicMock()
|
||||
geom.isEmpty.return_value = False
|
||||
feat.geometry.return_value = geom
|
||||
layer.getFeatures.return_value = [feat]
|
||||
|
||||
# QgsGeometry.unaryUnion wirft eine Exception → leere Liste erwartet
|
||||
with patch(f"{_MOD}.QgsGeometry") as mock_geom_cls:
|
||||
mock_geom_cls.unaryUnion.side_effect = ImportError("kein QGIS")
|
||||
result = _extrahiere_knickpunkte(layer)
|
||||
self.assertEqual(result, [])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: knickpunkt_pruefung – Eingabevalidierung
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestKnickpunktPruefungInputvalidierung(unittest.TestCase):
|
||||
|
||||
def test_keine_knickpunkte_gibt_none_zurueck(self):
|
||||
bror = _make_layer()
|
||||
bror.getFeatures.return_value = []
|
||||
result = knickpunkt_pruefung(bror, MagicMock(), None)
|
||||
layer, identisch, kein_pta, fehlend = result
|
||||
self.assertIsNone(layer)
|
||||
self.assertEqual(identisch, 0)
|
||||
self.assertEqual(kein_pta, 0)
|
||||
self.assertEqual(fehlend, 0)
|
||||
|
||||
def test_kein_punktort_layer_gibt_none_zurueck(self):
|
||||
bror = _make_layer()
|
||||
result = knickpunkt_pruefung(bror, None, None, knickpunkte=[MagicMock()])
|
||||
layer, identisch, kein_pta, fehlend = result
|
||||
self.assertIsNone(layer)
|
||||
|
||||
def test_abbruch_via_progress_gibt_none_zurueck(self):
|
||||
bror = _make_layer()
|
||||
pta = _make_layer()
|
||||
prog = _make_progress(canceled_after=0) # sofort abbrechen
|
||||
result = knickpunkt_pruefung(
|
||||
bror, pta, None,
|
||||
progress=prog,
|
||||
knickpunkte=[MagicMock()],
|
||||
)
|
||||
self.assertIsNone(result[0])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: knickpunkt_pruefung – Klassifizierungslogik (mit QGIS-Mocks)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestKnickpunktPruefungKlassifizierung(unittest.TestCase):
|
||||
"""Testet die Grün/Rot/Orange-Klassifizierung mit gesteuertem QGIS-Mock."""
|
||||
|
||||
def _make_pta_layer_mit_punkt(self, x: float, y: float, fid: int = 1):
|
||||
"""Erstellt einen PunktortTA-Layer-Mock mit einem Punkt bei (x, y)."""
|
||||
layer = _make_layer(lid=f"PTA_{fid}")
|
||||
geom = MagicMock()
|
||||
geom.isEmpty.return_value = False
|
||||
pt = MagicMock()
|
||||
pt.x.return_value = x
|
||||
pt.y.return_value = y
|
||||
geom.asPoint.return_value = pt
|
||||
feat = MagicMock()
|
||||
feat.id.return_value = fid
|
||||
feat.geometry.return_value = geom
|
||||
layer.getFeatures.return_value = [feat]
|
||||
return layer
|
||||
|
||||
def _make_bror_layer(self, x: float = 0.0, y: float = 0.0):
|
||||
"""BROR-Layer mit einer minimalen Polygon-Geometrie."""
|
||||
layer = _make_layer()
|
||||
geom = MagicMock()
|
||||
geom.isEmpty.return_value = False
|
||||
geom.area.return_value = 10000.0
|
||||
feat = MagicMock()
|
||||
feat.geometry.return_value = geom
|
||||
layer.getFeatures.return_value = [feat]
|
||||
return layer
|
||||
|
||||
@patch(f"{_MOD}.QgsGeometry")
|
||||
@patch(f"{_MOD}._baue_pta_index")
|
||||
@patch(f"{_MOD}._erstelle_ergebnis_layer")
|
||||
@patch(f"qgis.core.QgsRectangle", create=True)
|
||||
@patch(f"qgis.core.QgsPointXY", create=True)
|
||||
def test_identischer_punkt_wird_gruen_gezaehlt(
|
||||
self, mock_ptxy, mock_rect, mock_erstelle, mock_index, mock_geom_cls
|
||||
):
|
||||
"""PunktortTA exakt am Knickpunkt → identisch_count == 1."""
|
||||
# Knickpunkt bei (100, 200)
|
||||
knick = MagicMock()
|
||||
knick.x.return_value = 100.0
|
||||
knick.y.return_value = 200.0
|
||||
|
||||
# PunktortTA ebenfalls bei (100, 200) – exakter Treffer
|
||||
pta_geom = MagicMock()
|
||||
pta_geom.isEmpty.return_value = False
|
||||
pta_pt = MagicMock()
|
||||
pta_pt.x.return_value = 100.0
|
||||
pta_pt.y.return_value = 200.0
|
||||
pta_geom.asPoint.return_value = pta_pt
|
||||
|
||||
# Spatial-Index gibt Feature-ID 1 zurück
|
||||
mock_idx = MagicMock()
|
||||
mock_idx.intersects.return_value = [1]
|
||||
mock_index.return_value = (mock_idx, {1: pta_geom})
|
||||
|
||||
# Ergebnis-Layer
|
||||
pr = MagicMock()
|
||||
pr.addFeatures.return_value = True
|
||||
ergebnis = MagicMock()
|
||||
ergebnis.dataProvider.return_value = pr
|
||||
ergebnis.isValid.return_value = True
|
||||
mock_erstelle.return_value = ergebnis
|
||||
|
||||
bror = self._make_bror_layer()
|
||||
pta = self._make_pta_layer_mit_punkt(100.0, 200.0)
|
||||
|
||||
result = knickpunkt_pruefung(
|
||||
bror, pta, None, knickpunkte=[knick]
|
||||
)
|
||||
_, identisch, kein_pta, _ = result
|
||||
self.assertEqual(identisch, 1)
|
||||
self.assertEqual(kein_pta, 0)
|
||||
|
||||
@patch(f"{_MOD}.QgsGeometry")
|
||||
@patch(f"{_MOD}._baue_pta_index")
|
||||
@patch(f"{_MOD}._erstelle_ergebnis_layer")
|
||||
@patch(f"qgis.core.QgsRectangle", create=True)
|
||||
@patch(f"qgis.core.QgsPointXY", create=True)
|
||||
def test_kein_pta_an_knickpunkt_wird_rot_gezaehlt(
|
||||
self, mock_ptxy, mock_rect, mock_erstelle, mock_index, mock_geom_cls
|
||||
):
|
||||
"""Kein PunktortTA am Knickpunkt → kein_pta_count == 1."""
|
||||
knick = MagicMock()
|
||||
knick.x.return_value = 100.0
|
||||
knick.y.return_value = 200.0
|
||||
|
||||
# Spatial-Index gibt leere Liste zurück → kein Treffer
|
||||
mock_idx = MagicMock()
|
||||
mock_idx.intersects.return_value = []
|
||||
mock_index.return_value = (mock_idx, {})
|
||||
|
||||
pr = MagicMock()
|
||||
pr.addFeatures.return_value = True
|
||||
ergebnis = MagicMock()
|
||||
ergebnis.dataProvider.return_value = pr
|
||||
ergebnis.isValid.return_value = True
|
||||
mock_erstelle.return_value = ergebnis
|
||||
|
||||
bror = self._make_bror_layer()
|
||||
pta = self._make_pta_layer_mit_punkt(999.0, 999.0) # weit weg
|
||||
|
||||
result = knickpunkt_pruefung(
|
||||
bror, pta, None, knickpunkte=[knick]
|
||||
)
|
||||
_, identisch, kein_pta, _ = result
|
||||
self.assertEqual(identisch, 0)
|
||||
self.assertEqual(kein_pta, 1)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: _baue_flst_vertex_set
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestBaueFlstVertexSet(unittest.TestCase):
|
||||
|
||||
def test_none_layer_gibt_leeres_set(self):
|
||||
from sn_verfahrensgebiet.functions.knickpunkt_pruefung import _baue_flst_vertex_set
|
||||
result = _baue_flst_vertex_set(None, MagicMock())
|
||||
self.assertEqual(result, set())
|
||||
|
||||
def test_layer_ohne_schnitt_mit_puffer_ignoriert(self):
|
||||
from sn_verfahrensgebiet.functions.knickpunkt_pruefung import _baue_flst_vertex_set
|
||||
layer = _make_layer()
|
||||
geom = MagicMock()
|
||||
geom.isEmpty.return_value = False
|
||||
geom.intersects.return_value = False # kein Schnitt
|
||||
feat = MagicMock()
|
||||
feat.geometry.return_value = geom
|
||||
layer.getFeatures.return_value = [feat]
|
||||
buffer_geom = MagicMock()
|
||||
result = _baue_flst_vertex_set(layer, buffer_geom)
|
||||
self.assertEqual(result, set())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user