# 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_KSEINAUS, | | TEST_OMNIFLO) | | | | v v | tests/output/ | kreisel_results.json (immer) | kreisel_tests.dxf (save: dxf) | foerderer_results.json (immer) | foerderer_tests.dwg (save: dwg) | kseinaus_results.json (immer) | (keine Zeichnung) (save: null) | omniflo_results.json (immer) | omniflo_tests.dxf (save: dxf) | omniflo_export.csv | | bin\run_tests.bat --runall | pytest -v | test_kreisel.py test_omniflo.py ``` ## 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": "kseinaus", "save": null }, { "name": "omniflo", "save": "dxf" } ] ``` 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). Die Zeichnung bleibt offen und kann 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_kseinaus.lsp # LISP-Testrunner: C:TEST_KSEINAUS test_omniflo.lsp # LISP-Testrunner: C:TEST_OMNIFLO test_omniflo.py # pytest-Validierung der Omniflo-Ergebnisse 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.dxf kseinaus_results.json kseinaus_tests.dxf omniflo_results.json omniflo_tests.dxf omniflo_export.csv # CSV-Export (Omniflo-spezifisch) 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** (Omniflo: Spalten, Merkmale, 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: Merkmale, Datentypen, Vollstaendigkeit (ohne BricsCAD) | | `TestOmnifloExportCSV` | CSV aus BricsCAD: Header, Spalten, JSON-Merkmale | ## 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 |