Gefällestrecke nach dem gleichen Muster bedaten wie die anderen Tests. Eingabedaten aus dem Test in json Datei gezogen. Ergänzung des Testsfalls durch Ergänzung des testsdata/*_tests.json möglich
This commit is contained in:
@@ -123,3 +123,34 @@ def kreisel_ref_dxf():
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("kreisel_ref.dxf nicht vorhanden - Referenz noch nicht erstellt")
|
||||
return ezdxf.readfile(path)
|
||||
|
||||
|
||||
# --- Gefaellestrecke Fixtures ---
|
||||
|
||||
@pytest.fixture
|
||||
def gefaellestrecke_testdata():
|
||||
"""Laedt die Gefaellestrecke-Testdefinitionen aus testdata/gefaellestrecke_tests.json."""
|
||||
path = os.path.join(_testdata_dir(), "gefaellestrecke_tests.json")
|
||||
data = _load_json(path)
|
||||
return [t for t in data if "test_id" in t]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gefaellestrecke_results():
|
||||
"""Laedt die Gefaellestrecke-Ergebnisse aus output/gefaellestrecke_results.json."""
|
||||
path = os.path.join(_output_dir(), "gefaellestrecke_results.json")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("gefaellestrecke_results.json nicht vorhanden - TEST_GEFAELLESTRECKE in BricsCAD ausfuehren")
|
||||
data = _load_json(path)
|
||||
if not data:
|
||||
pytest.skip("gefaellestrecke_results.json ist leer")
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gefaellestrecke_dxf():
|
||||
"""Laedt die erzeugte Gefaellestrecke-DXF aus output/gefaellestrecke_tests.dxf."""
|
||||
path = os.path.join(_output_dir(), "gefaellestrecke_tests.dxf")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("gefaellestrecke_tests.dxf nicht vorhanden - TESTRUN in BricsCAD ausfuehren")
|
||||
return ezdxf.readfile(path)
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
y-offset y-abstand idx
|
||||
anz-gebaut anz-nicht-gebaut results-list
|
||||
startpunkt vf-nummer hoehe-von hoehe-bis lastEnt
|
||||
sub-ent sub-typ
|
||||
tests-out-dir old-dxfm-results)
|
||||
|
||||
;; Bibliothek initialisieren (laedt Block-Library und extrahiert Masse)
|
||||
@@ -103,7 +104,7 @@
|
||||
;; Konfiguration aus erstem Eintrag (ohne test_id)
|
||||
(setq cfg (car testdaten))
|
||||
(setq y-abstand (ssg-val cfg "y_abstand"))
|
||||
(if (or (null y-abstand) (= y-abstand 0)) (setq y-abstand 400))
|
||||
(if (or (null y-abstand) (= y-abstand 0)) (setq y-abstand 800))
|
||||
|
||||
;; Nur Eintraege mit test_id sind Testfaelle
|
||||
(setq tests
|
||||
@@ -174,6 +175,19 @@
|
||||
hoehe-von hoehe-bis deltaH deltaL L_VF L_GF
|
||||
richtung best-winkel startpunkt lastEnt)
|
||||
|
||||
;; lastEnt hinter den SEQEND des VF-Blocks schieben.
|
||||
;; Ohne diesen Schritt wuerde entnext vom INSERT-Entity
|
||||
;; in die ATTRIB-Kette des gerade erstellten VF-Blocks
|
||||
;; springen und diese beim naechsten _.-BLOCK-Aufruf
|
||||
;; in den neuen Block verschieben (Datenverlust).
|
||||
(setq lastEnt (entlast))
|
||||
(setq sub-ent (entnext lastEnt))
|
||||
(while (and sub-ent
|
||||
(not (equal (cdr (assoc 0 (entget sub-ent))) "SEQEND")))
|
||||
(setq lastEnt sub-ent)
|
||||
(setq sub-ent (entnext lastEnt)))
|
||||
(if sub-ent (setq lastEnt sub-ent))
|
||||
|
||||
(setq results-list (cons
|
||||
(foerderer:test-result-json test-id richtung deltaL deltaH
|
||||
"GEBAUT" best-winkel L_GF L_VF)
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_gefaellestrecke.py - Validiert GEFAELLESTRECKE Testergebnisse.
|
||||
|
||||
Prueft:
|
||||
1. Status-Werte und Vollstaendigkeit (gefaellestrecke_results.json)
|
||||
2. Mathematische Invarianten (Hoehen, Winkel, deltaH)
|
||||
3. Konsistenz zwischen den Modi (Modus 1 Richtungsunabhaengigkeit,
|
||||
Modus 3 uebernimmt deltaH/deltaL aus Modus 2)
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hilfsfunktionen
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _testdata_dir():
|
||||
return os.getenv("DXFM_TESTDATA",
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "testdata"))
|
||||
|
||||
|
||||
def _output_dir():
|
||||
return os.getenv("DXFM_TESTOUT",
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "output"))
|
||||
|
||||
|
||||
def _load_json(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _load_gefaellestrecke_results():
|
||||
path = os.path.join(_output_dir(), "gefaellestrecke_results.json")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip(
|
||||
"gefaellestrecke_results.json nicht vorhanden - "
|
||||
"TEST_GEFAELLESTRECKE in BricsCAD ausfuehren"
|
||||
)
|
||||
data = _load_json(path)
|
||||
if not data:
|
||||
pytest.skip("gefaellestrecke_results.json ist leer")
|
||||
return data
|
||||
|
||||
|
||||
def _load_gefaellestrecke_testdata():
|
||||
path = os.path.join(_testdata_dir(), "gefaellestrecke_tests.json")
|
||||
return _load_json(path)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Klasse 1: Status und Vollstaendigkeit
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGefaellestreckeStatus:
|
||||
"""Prueft Status-Werte und Vollstaendigkeit der Ergebnisse."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
self.results = _load_gefaellestrecke_results()
|
||||
testdata = _load_gefaellestrecke_testdata()
|
||||
self.tests = [t for t in testdata if "test_id" in t]
|
||||
self.results_by_id = {r["test_id"]: r for r in self.results}
|
||||
|
||||
def test_alle_testfaelle_vorhanden(self):
|
||||
"""Alle Testfaelle muessen in den Ergebnissen vorhanden sein."""
|
||||
expected_ids = {t["test_id"] for t in self.tests}
|
||||
result_ids = {r["test_id"] for r in self.results}
|
||||
missing = expected_ids - result_ids
|
||||
assert not missing, f"Fehlende Testfaelle in Ergebnissen: {missing}"
|
||||
|
||||
def test_status_werte_gueltig(self):
|
||||
"""Status muss GEBAUT oder FEHLER (bzw. FEHLER_LINIE) sein."""
|
||||
gueltige_status = {"GEBAUT", "FEHLER", "FEHLER_LINIE"}
|
||||
for r in self.results:
|
||||
assert r["status"] in gueltige_status, (
|
||||
f'{r["test_id"]}: Unbekannter Status "{r["status"]}"'
|
||||
)
|
||||
|
||||
def test_alle_modus1_gebaut(self):
|
||||
"""Alle vier Modus-1-Richtungen (hz=0/90/180/270) muessen GEBAUT sein."""
|
||||
modus1 = [r for r in self.results if r["modus"] == 1]
|
||||
assert len(modus1) == 4, f"Erwartet 4 Modus-1-Testfaelle, gefunden {len(modus1)}"
|
||||
for r in modus1:
|
||||
assert r["status"] == "GEBAUT", (
|
||||
f'{r["test_id"]}: erwartet GEBAUT, ist "{r["status"]}"'
|
||||
)
|
||||
|
||||
def test_modus2_und_modus3_gebaut(self):
|
||||
"""Modus 2 und Modus 3 muessen GEBAUT sein."""
|
||||
for test_id in ("M2_hz000", "M3_hz270"):
|
||||
r = self.results_by_id.get(test_id)
|
||||
assert r is not None, f"{test_id} fehlt in den Ergebnissen"
|
||||
assert r["status"] == "GEBAUT", (
|
||||
f'{test_id}: erwartet GEBAUT, ist "{r["status"]}"'
|
||||
)
|
||||
|
||||
def test_mindestens_ein_gebaut(self):
|
||||
"""Mindestens ein Testfall muss GEBAUT sein."""
|
||||
gebaut = [r for r in self.results if r["status"] == "GEBAUT"]
|
||||
assert len(gebaut) > 0, "Keine einzige Gefaellestrecke konnte gebaut werden"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Klasse 2: Mathematische Invarianten
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGefaellestreckeMathematik:
|
||||
"""Prueft mathematische Korrektheit der berechneten Werte."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
self.results = _load_gefaellestrecke_results()
|
||||
self.gebaut = [r for r in self.results if r["status"] == "GEBAUT"]
|
||||
self.results_by_id = {r["test_id"]: r for r in self.results}
|
||||
|
||||
def test_gebaut_hat_pflichtfelder(self):
|
||||
"""GEBAUT-Ergebnisse muessen alle Kernfelder enthalten."""
|
||||
pflichtfelder = {
|
||||
"modus", "hz_grad", "deltaL_mm", "winkel_grad",
|
||||
"hoehe_von_mm", "hoehe_bis_mm", "delta_h_mm",
|
||||
}
|
||||
for r in self.gebaut:
|
||||
missing = pflichtfelder - set(r.keys())
|
||||
assert not missing, f'{r["test_id"]}: fehlende Felder {missing}'
|
||||
|
||||
def test_deltaL_positiv(self):
|
||||
"""deltaL_mm muss fuer alle GEBAUT-Faelle groesser als 0 sein."""
|
||||
for r in self.gebaut:
|
||||
assert r["deltaL_mm"] > 0, (
|
||||
f'{r["test_id"]}: deltaL_mm={r["deltaL_mm"]} muss > 0 sein'
|
||||
)
|
||||
|
||||
def test_winkel_positiv(self):
|
||||
"""Der berechnete Gefaellewinkel muss groesser als 0 Grad sein."""
|
||||
for r in self.gebaut:
|
||||
assert r["winkel_grad"] > 0, (
|
||||
f'{r["test_id"]}: winkel_grad={r["winkel_grad"]} muss > 0 sein'
|
||||
)
|
||||
|
||||
def test_delta_h_konsistent_mit_hoehen(self):
|
||||
"""delta_h_mm muss der absoluten Differenz von hoehe_von/hoehe_bis entsprechen."""
|
||||
for r in self.gebaut:
|
||||
erwartet = abs(r["hoehe_von_mm"] - r["hoehe_bis_mm"])
|
||||
assert erwartet == r["delta_h_mm"], (
|
||||
f'{r["test_id"]}: delta_h_mm={r["delta_h_mm"]}, '
|
||||
f'erwartet |{r["hoehe_von_mm"]}-{r["hoehe_bis_mm"]}|={erwartet}'
|
||||
)
|
||||
|
||||
def test_hoehe_bis_kleiner_hoehe_von(self):
|
||||
"""Bei einer Gefaellestrecke muss die Endhoehe unter der Starthoehe liegen."""
|
||||
for r in self.gebaut:
|
||||
assert r["hoehe_bis_mm"] < r["hoehe_von_mm"], (
|
||||
f'{r["test_id"]}: hoehe_bis_mm={r["hoehe_bis_mm"]} '
|
||||
f'nicht < hoehe_von_mm={r["hoehe_von_mm"]}'
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Klasse 3: Konsistenz zwischen den Modi
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGefaellestreckeModusKonsistenz:
|
||||
"""Prueft Zusammenhaenge zwischen Modus 1/2/3 (Richtungsunabhaengigkeit,
|
||||
Uebernahme von deltaH/deltaL aus Modus 2 in Modus 3)."""
|
||||
|
||||
# Toleranz fuer die 2-Pass-Winkelkorrektur in Modus 3 (mm)
|
||||
DELTA_H_TOLERANZ_MM = 5
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
self.results = _load_gefaellestrecke_results()
|
||||
self.results_by_id = {r["test_id"]: r for r in self.results}
|
||||
self.testdata = _load_gefaellestrecke_testdata()
|
||||
self.tests_by_id = {t["test_id"]: t for t in self.testdata if "test_id" in t}
|
||||
|
||||
def test_modus1_richtungsunabhaengig(self):
|
||||
"""Alle vier Modus-1-Richtungen muessen dieselbe Hoehe/Winkel liefern,
|
||||
da nur die Fahrtrichtung (hz), nicht die Geometrie variiert."""
|
||||
modus1 = [r for r in self.results
|
||||
if r["modus"] == 1 and r["status"] == "GEBAUT"]
|
||||
assert len(modus1) >= 2, "Zu wenige Modus-1-Ergebnisse fuer Vergleich"
|
||||
referenz = modus1[0]
|
||||
for r in modus1[1:]:
|
||||
assert r["delta_h_mm"] == referenz["delta_h_mm"], (
|
||||
f'{r["test_id"]}: delta_h_mm={r["delta_h_mm"]} weicht von '
|
||||
f'{referenz["test_id"]}={referenz["delta_h_mm"]} ab (Richtungsabhaengigkeit!)'
|
||||
)
|
||||
assert r["winkel_grad"] == referenz["winkel_grad"], (
|
||||
f'{r["test_id"]}: winkel_grad={r["winkel_grad"]} weicht von '
|
||||
f'{referenz["test_id"]}={referenz["winkel_grad"]} ab'
|
||||
)
|
||||
|
||||
def test_modus3_uebernimmt_deltaH_aus_modus2(self):
|
||||
"""Modus 3 (Linienzug mit 2-Pass-Korrektur) muss innerhalb einer kleinen
|
||||
Toleranz dieselbe Hoehendifferenz wie die Quelle aus Modus 2 erreichen."""
|
||||
m3_test = self.tests_by_id.get("M3_hz270")
|
||||
if m3_test is None:
|
||||
pytest.skip("M3_hz270 nicht in Testdaten definiert")
|
||||
quelle_id = m3_test.get("deltaH_quelle")
|
||||
m2 = self.results_by_id.get(quelle_id)
|
||||
m3 = self.results_by_id.get("M3_hz270")
|
||||
if m2 is None or m3 is None:
|
||||
pytest.skip("M2- oder M3-Ergebnis fehlt")
|
||||
if m2["status"] != "GEBAUT" or m3["status"] != "GEBAUT":
|
||||
pytest.skip("M2 oder M3 nicht GEBAUT")
|
||||
abweichung = abs(m3["delta_h_mm"] - m2["delta_h_mm"])
|
||||
assert abweichung <= self.DELTA_H_TOLERANZ_MM, (
|
||||
f'M3_hz270: delta_h_mm={m3["delta_h_mm"]} weicht um {abweichung} mm '
|
||||
f'von {quelle_id}={m2["delta_h_mm"]} ab '
|
||||
f'(Toleranz {self.DELTA_H_TOLERANZ_MM} mm)'
|
||||
)
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"y_abstand": 400
|
||||
"y_abstand": 600
|
||||
},
|
||||
{
|
||||
"test_id": "VF_Auf_0",
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
[
|
||||
{
|
||||
"deltaL_mm": 10000,
|
||||
"winkel_grad": 3.0,
|
||||
"z_start_mm": 5000
|
||||
},
|
||||
{
|
||||
"test_id": "M1_hz000",
|
||||
"modus": 1,
|
||||
"hz_grad": 0.0,
|
||||
"start_mm": [0, 0, 5000],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
},
|
||||
{
|
||||
"test_id": "M1_hz090",
|
||||
"modus": 1,
|
||||
"hz_grad": 90.0,
|
||||
"start_mm": [15000, 0, 5000],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
},
|
||||
{
|
||||
"test_id": "M1_hz180",
|
||||
"modus": 1,
|
||||
"hz_grad": 180.0,
|
||||
"start_mm": [0, -15000, 5000],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
},
|
||||
{
|
||||
"test_id": "M1_hz270",
|
||||
"modus": 1,
|
||||
"hz_grad": 270.0,
|
||||
"start_mm": [15000, -15000, 5000],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
},
|
||||
{
|
||||
"test_id": "M2_hz000",
|
||||
"modus": 2,
|
||||
"hz_grad": 0.0,
|
||||
"start_mm": [0, -35000, 5000],
|
||||
"linie_start_mm": [0, -35000, 0],
|
||||
"linie_ende_mm": [10000, -35000, 0],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
},
|
||||
{
|
||||
"test_id": "M3_hz270",
|
||||
"modus": 3,
|
||||
"richtung": "Sued",
|
||||
"start_mm": [0, -50000, 5000],
|
||||
"deltaH_quelle": "M2_hz000",
|
||||
"deltaL_quelle": "M2_hz000",
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user