Files
dxfmakros/tests/README.md
T

398 lines
13 KiB
Markdown

# 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) |
|
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 `<name>_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
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_<name>.lsp` laden und `TEST_<NAME>` ausfuehren
3. JSON-Export aufrufen: `<name>: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/<modul>_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_<modul>.lsp
;; Konvention: Basisname <modul> bestimmt Befehl, DXF und Export-Funktion
;; Export-Funktion (wird von test_run_all.lsp aufgerufen)
(defun <modul>:export-results (tests-out-dir / out-json f first)
(if (null *<modul>-test-results*)
(princ "\n Keine <modul>-Ergebnisse vorhanden.")
(progn
(vl-mkdir tests-out-dir)
(setq out-json (strcat tests-out-dir "/<modul>_results.json"))
;; ... JSON-Array schreiben ...
)
)
)
(defun c:TEST_<MODUL> (/ ...)
(ssg-start "TEST_<MODUL>" '(("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/<modul>_tests.json")))
;; 2. Testfaelle ausfuehren, Ergebnisse sammeln
(foreach eintrag testfaelle
;; ... Script-Funktion aufrufen, Ergebnis pruefen ...
)
;; 3. Ergebnisse in globaler Variable speichern
(setq *<modul>-test-results* (reverse results-list))
(ssg-end)
)
```
Dann den Basisnamen in `tests/alltests.json` eintragen:
```json
{ "name": "<modul>", "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 `<name>` in `alltests.json` entstehen durch Konvention:
| Element | Schema | Beispiel fuer `"name": "kreisel"` |
| --- | --- | --- |
| LISP-Datei | `tests/test_<name>.lsp` | `tests/test_kreisel.lsp` |
| BricsCAD-Befehl | `TEST_<NAME>` (Grossbuchstaben) | `TEST_KREISEL` |
| Export-Funktion | `<name>:export-results` | `kreisel:export-results` |
| JSON-Ergebnis | `output/<name>_results.json` | `output/kreisel_results.json` |
| DXF/DWG-Datei | `output/<name>_tests.dxf/.dwg` | `output/kreisel_tests.dxf` |
Die Export-Funktion **muss** exakt so heissen: `<name>: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_<modul>.py` erstellen:
```python
import pytest
class TestModulAttributes:
"""Prueft Attribute gegen Testdefinitionen."""
def test_all_executed(self, <modul>_results):
"""Alle Testfaelle muessen ausgefuehrt worden sein."""
# ...
def test_attribute_values(self, <modul>_testdata, <modul>_results):
"""Attributwerte muessen mit Erwartungen uebereinstimmen."""
# ...
class TestModulGeometry:
"""Prueft DXF-Geometrie mit ezdxf."""
def test_blocks_exist(self, <modul>_dxf):
"""Bloecke muessen im Modelspace vorhanden sein."""
# ...
class TestModulReference:
"""Vergleicht gegen Referenz."""
def test_matches_reference(self, <modul>_dxf, <modul>_ref_dxf):
# ...
```
### Schritt 4: Fixtures in conftest.py ergaenzen
Neue Fixtures fuer Testdaten, Ergebnisse und DXF in `conftest.py` hinzufuegen:
```python
@pytest.fixture
def <modul>_testdata():
path = os.path.join(_testdata_dir(), "<modul>_tests.json")
return _load_json(path)
@pytest.fixture
def <modul>_results():
path = os.path.join(_output_dir(), "<modul>_results.json")
if not os.path.exists(path):
pytest.skip("<modul>_results.json nicht vorhanden")
return _load_json(path)
@pytest.fixture
def <modul>_dxf():
path = os.path.join(_output_dir(), "<modul>_tests.dxf")
if not os.path.exists(path):
pytest.skip("<modul>_tests.dxf nicht vorhanden")
return ezdxf.readfile(path)
@pytest.fixture
def <modul>_ref_dxf():
path = os.path.join(_reference_dir(), "<modul>_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 |