# Tests - SSG_LIB Testarchitektur ## Uebersicht Die Tests folgen einer **zweistufigen Architektur**: 1. **LISP-Testrunner** (`.lsp`) — laufen in BricsCAD, erzeugen Ergebnisdateien 2. **Python-Validierung** (`.py`) — pruefen die Ergebnisse mit pytest + ezdxf ``` tests/alltests.json | v BricsCAD Kommandozeile | | | SSG_RUN_ALL_TESTS | | (TEST_KREISEL, | | TEST_FOERDERER, | | TEST_OMNIFLO, | | TEST_GEFAELLESTRECKE) | | | | v v | tests/output/ | kreisel_results.json (immer) | kreisel_tests.dxf (save: dxf) | foerderer_results.json (immer) | foerderer_tests.dwg (save: dwg) | omniflo_results.json (immer) | omniflo_tests.dxf (save: dxf) | gefaellestrecke_results.json | gefaellestrecke_tests.dwg | | bin\run_tests.bat --runall | pytest -v | test_kreisel.py test_omniflo.py test_foerderer.py ``` **Jedes Testmodul laeuft in einer eigenen, neuen Zeichnung** (statt die aktuelle zu leeren/wiederzuverwenden). `SSG_RUN_ALL_TESTS` fragt beim Start interaktiv nach dem gewuenschten Modus: - **Kontroll-Modus (Default, Enter/Nein)**: Zeichnung wird gespeichert, per `_.ZOOM _Extents` auf den Inhalt gezoomt und bleibt als eigener Tab offen — der naechste Test startet in einer weiteren neuen Zeichnung. Am Ende des Laufs sind alle Testzeichnungen gleichzeitig offen und direkt sichtbar. Sinnvoll, wenn neue/geaenderte Testfaelle visuell geprueft werden sollen. `c:EXPORTCSV`/`c:EXPORTSIVAS` laufen **nicht** automatisch mit — CSV/Sivas- Export danach bei Bedarf manuell ueber `TEST_EXPORT_ALL` (`tests/test_export_all.lsp`, Menuepunkt "Export CSV/Sivas ALL"), das alle vorhandenen DXF/DWG-Dateien aus `tests/output/` einliest und je Datei `_export.csv`/`_sivas.csv` erzeugt. - **Automatik-Modus (Ja)**: Zeichnung wird gespeichert, direkt per `c:EXPORTCSV`/`c:EXPORTSIVAS` exportiert (`_tests_export.csv`/ `_tests_sivas.csv`) und danach sofort wieder geschlossen. Kompletter Lauf ohne manuellen Zusatzschritt, aber ohne gleichzeitige visuelle Kontrolle aller Zeichnungen am Ende. Sinnvoll fuer reine CI-artige Validierung. ## alltests.json — Testregistry Die Datei `tests/alltests.json` ist die zentrale Registry aller Test-Module. `SSG_RUN_ALL_TESTS` liest sie beim Start — die Testliste ist **nicht** hartcodiert in `test_run_all.lsp`. ```json [ { "name": "kreisel", "save": "dxf" }, { "name": "foerderer", "save": "dwg" }, { "name": "omniflo", "save": "dxf" }, { "name": "gefaellestrecke", "save": "dwg" } ] ``` Felder pro Eintrag: | Feld | Typ | Bedeutung | | --- | --- | --- | | `name` | String | Basisname des Testmoduls (Pflicht) | | `save` | `"dxf"` / `"dwg"` / `null` | Ob und als welches Format die Zeichnung gespeichert wird | **JSON-Export laeuft immer** — unabhaengig vom `save`-Feld. Die Datei `_results.json` wird in `tests/output/` geschrieben, sobald der Test abgeschlossen ist. **Wann welches `save` verwenden:** - `"dxf"` — wenn ein Python-Test (ezdxf) die Geometrie der Zeichnung pruefen soll - `"dwg"` — wenn die Zeichnung zur manuellen Kontrolle oder als DWG-Referenz erhalten bleiben soll - `null` (oder Feld weglassen) — wenn nur der JSON-Export benoetigt wird ## Ablauf fuer den Anwender 1. **BricsCAD starten** (ueber `bin\start_briscad.bat`) und die LISP-Testrunner ausfuehren (siehe Abschnitt "LISP-Tests in BricsCAD ausfuehren"). Die Testrunner erzeugen Bloecke in der Zeichnung und speichern Ergebnisse nach `tests/output/`. 2. **Optische Kontrolle** in BricsCAD — pruefen ob die erzeugten Bloecke korrekt aussehen (Positionen, Drehungen, Beschriftungen). Jede Testzeichnung bleibt als eigener Tab offen (auf den Inhalt gezoomt) und kann direkt visuell inspiziert werden. 3. **Kommandozeile**: Ergebnisse validieren oder als neue Referenz speichern: - `bin\run_tests.bat --runall` — pytest prueft die Ergebnisse aus `output/` gegen Testdefinitionen und Referenzdaten - `bin\run_tests.bat --set-as-reference` — nach erfolgreicher Pruefung die aktuellen Ergebnisse als neue Referenz uebernehmen (kopiert `output/` nach `reference/`) ## Verzeichnisstruktur ``` tests/ conftest.py # Gemeinsame pytest-Fixtures (Pfade, JSON/DXF-Laden) create_testbase.py # Erzeugt Basis-DXF (optional, wird nicht mehr benoetigt) requirements.txt # Python-Abhaengigkeiten (ezdxf, pytest) test_run_all.lsp # Fuehrt alle Tests aus: C:SSG_RUN_ALL_TESTS test_kreisel.lsp # LISP-Testrunner: C:TEST_KREISEL test_kreisel.py # pytest-Validierung der Kreisel-Ergebnisse test_foerderer.lsp # LISP-Testrunner: C:TEST_FOERDERER test_omniflo.lsp # LISP-Testrunner: C:TEST_OMNIFLO test_omniflo.py # pytest-Validierung der Omniflo-Ergebnisse test_export_all.lsp # LISP-Testrunner: C:TEST_EXPORT_ALL (manuell, nicht in alltests.json) testdata/ kreisel_tests.json # Testfall-Definitionen fuer Kreisel omniflo_tests.json # Testfall-Definitionen fuer Omniflo output/ # Ergebnisse aus BricsCAD (nicht in Git) kreisel_results.json # JSON-Ergebnisse pro Testmodul kreisel_tests.dxf # DXF-Zeichnung pro Testmodul foerderer_results.json foerderer_tests.dwg omniflo_results.json omniflo_tests.dxf gefaellestrecke_results.json gefaellestrecke_tests.dwg export_all_results.json # nur nach manuellem TEST_EXPORT_ALL-Lauf _export.csv # EXPORTCSV je Quelldatei, nur nach TEST_EXPORT_ALL _sivas.csv # EXPORTSIVAS je Quelldatei, nur nach TEST_EXPORT_ALL reference/ # Abgenommene Referenzdaten (in Git) kreisel_ref.dxf ... ``` ## Workflow ### 1. LISP-Tests in BricsCAD ausfuehren BricsCAD starten (ueber `bin\start_briscad.bat`) und Module laden: ```lisp (load (strcat (getenv "DXFMAKRO") "/Lisp/ssg_load.lsp")) ``` Dann alle Tests auf einmal ausfuehren: ```lisp ;; Alle Tests ausfuehren (empfohlen) (load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) SSG_RUN_ALL_TESTS ``` `SSG_RUN_ALL_TESTS` liest die Testliste aus `tests/alltests.json` und fuehrt fuer jedes Modul aus: 1. Zeichnung leeren (ERASE ALL + PURGE x5) — saubere Basis fuer jeden Test 2. `test_.lsp` laden und `TEST_` ausfuehren 3. JSON-Export aufrufen: `:export-results` — **immer**, unabhaengig von `save` 4. Zeichnung speichern — nur wenn `"save"` in `alltests.json` gesetzt ist (`"dxf"` oder `"dwg"`) 5. Bei Fehler: weiter mit naechstem Test (`vl-catch-all-apply`) **Einzeltest** (z.B. nur Kreisel pruefen): ```lisp ;; Test-Datei laden und Befehl ausfuehren (load (strcat (getenv "DXFMAKRO") "/tests/test_kreisel.lsp")) TEST_KREISEL ;; JSON-Export manuell aufrufen: (kreisel:export-results (strcat (getenv "DXFMAKRO") "/tests/output")) ``` **Unterschied SSG_RUN_ALL_TESTS vs. Einzeltest:** | | SSG_RUN_ALL_TESTS | Einzeltest | | --- | --- | --- | | Testliste | aus `alltests.json` | manuell gewaehlter Test | | Zeichnung leeren | automatisch vor jedem Test | nein (Zeichnung bleibt) | | JSON-Export | automatisch nach jedem Test | manuell aufrufen | | DXF/DWG speichern | gemaess `save` in `alltests.json` | manuell (SAVEAS / DXFOUT) | | Fehlerisolation | ja (Fehler unterbricht nicht weitere Tests) | nein | ### 2. Python-Validierung ausfuehren ```cmd bin\run_tests.bat --runall ``` Oder fuer ein einzelnes Testmodul: ```cmd bin\run_tests.bat --runall test_kreisel.py ``` Die Python-Tests pruefen: - **Attribut-Werte** gegen die Testdefinitionen (JSON) - **DXF-Geometrie** mit ezdxf (Bloecke, Positionen, Radien) - **Referenz-Vergleich** gegen abgenommene Referenz-DXF - **CSV-Export** ueber `lib/export_csv.py` (Omniflo: Spalten, Merkmale, Sum-Zeile, Datentypen) Tests, deren Eingabedaten fehlen (z.B. kein BricsCAD-Lauf), werden automatisch uebersprungen (`pytest.skip`). ### 3. Referenz erstellen/aktualisieren Nach einem erfolgreichen Testlauf die Ergebnisse als Referenz speichern: ```cmd bin\run_tests.bat --set-as-reference ``` Kopiert alle Dateien aus `output/` nach `reference/`. Nur bei bestandenen Tests verwenden. ## Neuen Test erstellen ### Schritt 1: Testdaten definieren (JSON) Neue Testfaelle in `testdata/_tests.json` eintragen. Das Format ist ein **flaches JSON-Array** (kompatibel mit `omni:load-json`). **Kreisel-Beispiel** (`kreisel_tests.json`): ```json [ { "id": "KR_Insert_Neu", "function": "insert", "x": 5000, "y": 5000, "z": 2000, "abstand": 5000, "rotation": 0.0, "typ": "STANDARD", "expect_block_prefix": "KREISEL_", "expect_hoehe": "2000", "expect_kreiselart": "STANDARD" } ] ``` Felder: - `id` — Eindeutiger Name des Testfalls - `function` — `"insert"` oder `"connect"` - Eingabeparameter (`x`, `y`, `z`, `abstand`, `rotation`, `typ` fuer Insert; `start_x/y/z`, `end_x/y/z`, `typ` fuer Connect) - `expect_*` — Erwartete Ergebniswerte (fuer Python-Validierung) **Omniflo-Beispiel** (`omniflo_tests.json`): ```json [ { "sivasnr": "834372001", "type": "bogen", "description": "Bogen 90 Grad R200", "hoehe": 2000, "drehung": 0 } ] ``` ### Schritt 2: LISP-Testrunner erweitern (falls neues Modul) Fuer ein neues Modul eine neue `.lsp`-Datei erstellen nach dem Muster: ```lisp ;; test_.lsp ;; Konvention: Basisname bestimmt Befehl, DXF und Export-Funktion ;; Export-Funktion (wird von test_run_all.lsp aufgerufen) (defun :export-results (tests-out-dir / out-json f first) (if (null *-test-results*) (princ "\n Keine -Ergebnisse vorhanden.") (progn (vl-mkdir tests-out-dir) (setq out-json (strcat tests-out-dir "/_results.json")) ;; ... JSON-Array schreiben ... ) ) ) (defun c:TEST_ (/ ...) (ssg-start "TEST_" '(("OSMODE") ("ATTREQ") ("ATTDIA"))) (setvar "OSMODE" 0) (setvar "ATTREQ" 0) (setvar "ATTDIA" 0) ;; 1. Testdaten laden (setq testfaelle (omni:load-json (strcat (getenv "DXFMAKRO") "/tests/testdata/_tests.json"))) ;; 2. Testfaelle ausfuehren, Ergebnisse sammeln (foreach eintrag testfaelle ;; ... Script-Funktion aufrufen, Ergebnis pruefen ... ) ;; 3. Ergebnisse in globaler Variable speichern (setq *-test-results* (reverse results-list)) (ssg-end) ) ``` Dann den Basisnamen in `tests/alltests.json` eintragen: ```json { "name": "", "save": "dxf" } ``` `"save"` waehlen je nach Bedarf (`"dxf"`, `"dwg"` oder `null`). `test_run_all.lsp` leitet alle Namen aus dem `"name"`-Feld ab — **nichts** in `test_run_all.lsp` aendern. ### Namenskonventionen (werden von test_run_all.lsp automatisch abgeleitet) Aus dem Basisnamen `` in `alltests.json` entstehen durch Konvention: | Element | Schema | Beispiel fuer `"name": "kreisel"` | | --- | --- | --- | | LISP-Datei | `tests/test_.lsp` | `tests/test_kreisel.lsp` | | BricsCAD-Befehl | `TEST_` (Grossbuchstaben) | `TEST_KREISEL` | | Export-Funktion | `:export-results` | `kreisel:export-results` | | JSON-Ergebnis | `output/_results.json` | `output/kreisel_results.json` | | DXF/DWG-Datei | `output/_tests.dxf/.dwg` | `output/kreisel_tests.dxf` | Die Export-Funktion **muss** exakt so heissen: `:export-results` (Namespace-Notation mit Doppelpunkt). Sie nimmt genau einen Parameter entgegen: den Pfad zum Output-Verzeichnis. Wichtig: - `omni:load-json` erwartet ein **flaches JSON-Array** mit flachen Objekten (kein Nesting) - `omni:val` liest Werte aus den geladenen Eintraegen - Script-Funktionen (z.B. `kreisel-insert-script`) muessen **ohne User-Interaktion** funktionieren ### Schritt 3: Python-Validierung erstellen Neue `test_.py` erstellen: ```python import pytest class TestModulAttributes: """Prueft Attribute gegen Testdefinitionen.""" def test_all_executed(self, _results): """Alle Testfaelle muessen ausgefuehrt worden sein.""" # ... def test_attribute_values(self, _testdata, _results): """Attributwerte muessen mit Erwartungen uebereinstimmen.""" # ... class TestModulGeometry: """Prueft DXF-Geometrie mit ezdxf.""" def test_blocks_exist(self, _dxf): """Bloecke muessen im Modelspace vorhanden sein.""" # ... class TestModulReference: """Vergleicht gegen Referenz.""" def test_matches_reference(self, _dxf, _ref_dxf): # ... ``` ### Schritt 4: Fixtures in conftest.py ergaenzen Neue Fixtures fuer Testdaten, Ergebnisse und DXF in `conftest.py` hinzufuegen: ```python @pytest.fixture def _testdata(): path = os.path.join(_testdata_dir(), "_tests.json") return _load_json(path) @pytest.fixture def _results(): path = os.path.join(_output_dir(), "_results.json") if not os.path.exists(path): pytest.skip("_results.json nicht vorhanden") return _load_json(path) @pytest.fixture def _dxf(): path = os.path.join(_output_dir(), "_tests.dxf") if not os.path.exists(path): pytest.skip("_tests.dxf nicht vorhanden") return ezdxf.readfile(path) @pytest.fixture def _ref_dxf(): path = os.path.join(_reference_dir(), "_ref.dxf") if not os.path.exists(path): pytest.skip("Referenz nicht vorhanden") return ezdxf.readfile(path) ``` ## Testklassen ### test_kreisel.py | Klasse | Prueft | |---|---| | `TestKreiselAttributes` | Attribute (KREISELART, HOEHE), Block-Prefixe, Status | | `TestKreiselGeometry` | DXF-Bloecke, Positionen, Kreis-Radien (400mm) | | `TestKreiselReference` | Block-Anzahl und -Typen gegen Referenz-DXF | ### test_omniflo.py | Klasse | Prueft | |---|---| | `TestOmnifloExportUnit` | Mock-Export ueber `lib/export_csv.py`: Merkmale, Datentypen, Sum-Zeile, Vollstaendigkeit (ohne BricsCAD) | | `TestOmnifloResults` | omniflo_results.json aus BricsCAD: Testfaelle vollstaendig, Status OK, Hoehe | | `TestOmnifloReferenceCSV` | CSV-Export aus `lib/export_csv.py` gegen abgenommene Referenz-CSV | ## Umgebungsvariablen Werden von `bin\setenv.bat` gesetzt. Fuer Tests relevant: | Variable | Beschreibung | |---|---| | `DXFMAKRO` | Projektwurzel | | `DXFM_TESTS` | tests/-Verzeichnis | | `DXFM_TESTDATA` | testdata/-Verzeichnis | | `DXFM_TESTOUT` | output/-Verzeichnis | | `DXFM_TESTREF` | reference/-Verzeichnis | | `DXFM_OMNIFLO` | Pfad zu Omniflo-DXF-Dateien |