# Tests ## Tests ausführen Voraussetzung ist die Entwicklungsumgebung aus dem [README](../README.md): ```bash pip install -r requirements-dev.txt ``` Alle Tests ausführen (Dauer etwa 15 s): ```bash pytest ``` Mit Testabdeckung und Liste der nicht abgedeckten Zeilen: ```bash pytest --cov=sandbox --cov-report=term-missing ``` Nur die schnellen Tests (ohne Stresstest und Smoke-Test): ```bash pytest -m "not slow" ``` Lint und Formatierung prüfen: ```bash ruff check src tests ``` ```bash ruff format --check src tests ``` Ruff prüft neben Stil und typischen Fehlern auch, dass alle Module, Klassen und öffentlichen Funktionen einen Docstring (`D`) und Type Hints (`ANN`) haben und dass in Vergleichen keine Magic Numbers stehen (`PLR2004`). Die Konfiguration steht in `pyproject.toml`. ## Headless-Modus UI- und Integrationstests laufen **ohne sichtbares Fenster**. `tests/conftest.py` setzt vor dem ersten Import von pygame: ```python os.environ.setdefault("SDL_VIDEODRIVER", "dummy") os.environ.setdefault("SDL_AUDIODRIVER", "dummy") ``` SDL rendert dann in einen unsichtbaren Puffer und braucht weder Bildschirm noch Soundkarte. Die Tests laufen so auch in CI oder über SSH. Die UI-Tests bedienen die Anwendung wie ein Mensch: Sie legen echte pygame-Events (`MOUSEBUTTONDOWN`, `MOUSEMOTION`, `KEYDOWN` …) in die Event-Queue und rufen dann `App.frame()` auf. Die Hilfsfunktionen dafür stehen in `tests/app/driver.py`. So wird die ganze Kette getestet: Event → Sidebar/InputHandler → Werkzeuge → Physik → Zeichnen. Die Anwendung selbst lässt sich ebenfalls headless starten, etwa für Screenshots. PowerShell: ```powershell $env:SDL_VIDEODRIVER = "dummy"; python -m sandbox --demo --screenshot bild.png --seconds 2 ``` Bash: ```bash SDL_VIDEODRIVER=dummy python -m sandbox --demo --screenshot bild.png --seconds 2 ``` ## Was wird getestet? Die Erwartungswerte der Physik-Tests stammen aus der klassischen Mechanik – Impulserhaltung, Stoßgesetz, Coulomb-Reibung, Rollbedingung – und nicht aus der Implementierung selbst. ### Physik (`tests/physics/`, ohne pygame) | Datei | Inhalt | |---|---| | `test_vector.py` | alle Vektoroperationen, Nullvektor-Normalisierung ohne NaN, Unveränderlichkeit, Hash/Gleichheit, Kopieren/Pickle | | `test_bodies.py` | Fläche und Trägheitsmoment, **Masse = Fläche × Dichte**, statische Körper, Materialprüfung, Impulse mit/ohne Drehung, Energie und Impuls | | `test_collision.py` | jede Formpaarung mit Normalenrichtung, Tiefe und Kontaktpunkt; Randfälle (gleicher Mittelpunkt, Kreismittelpunkt im Rechteck, knapp verfehlte Ecke, Körper hinter der Wand); Zufallstest mit über 500 Kontakten: Verschieben um die Tiefe trennt die Körper | | `test_broadphase.py` | Sweep-and-Prune liefert dieselben Paare wie der Brute-Force-Vergleich | | `test_solver.py` | **elastischer Stoß:** Impuls- und Energieerhaltung, Lehrbuchformel; e = 0: gemeinsame Geschwindigkeit; **Energieverlust bei e < 1** exakt nach ΔE = ½·μ·(1 − e²)·v²; Restitutionsschwelle; schiefer Stoß ohne Reibung; Coulomb-Reibung (Bremsweg v²/2μg); Rutschen → Rollen mit ⅔·v₀; Rollwiderstand; Auflagekraft = m·g·dt; Impulsgrenzen; **Trennen überlappender Körper ohne Geschwindigkeit** | | `test_world.py` | **freier Fall ≈ ½·g·t²** (und exakt nach semi-implizitem Euler); **Ball bleibt am Boden liegen und verlässt die Welt nie**; Sprunghöhe = e² · Fallhöhe (1 %); ein vollkommen elastischer Ball springt über viele Sprünge immer wieder genau auf seine Fallhöhe; Energie steigt ohne äußere Kräfte nie; Stapel bleibt stehen; gemischter Haufen schläft ein; kein Tunneln bei Höchstgeschwindigkeit; Notbremse; Schlafen/Aufwecken, auch wenn die Basis eines schlafenden Stapels weggezogen oder weggestoßen wird (nichts schläft in der Luft weiter); Determinismus; **100 Körper × 1000 Schritte ohne NaN** (Stresstest) | | `test_timestep.py` | Akkumulator, keine verlorenen Schritte durch Rundung, Obergrenze gegen die "Spiral of Death" | | `test_sleep.py` | Union-Find, Inselbildung (Wände verbinden nicht), Einschlafen und Aufwachen nur als ganze Insel | ### Interaktion (`tests/interaction/`, ohne pygame) | Datei | Inhalt | |---|---| | `test_velocity_tracker.py` | exakte Messung konstanter Bewegung, Unabhängigkeit von der Bildrate, Abklingen beim Stillhalten, Glättung von Zittern | | `test_drag_and_tools.py` | Seitenverhältnis mit gleicher Fläche, Mindestgröße, Körper erzeugen, Obergrenze, Vorschaufarbe = echte Farbe; Greifen mit Versatz, Ziel in der Welt, Folgen, Geschwindigkeitsgrenze, **gezogener Körper schiebt andere weg**, gegen ein Hindernis gedrückter Körper zittert nicht, Werfen, Pause | | `test_demo.py` | Beispielszene passt in die Welt, der Turm bleibt stehen | ### Anwendung (`tests/app/`, headless mit pygame) | Datei | Inhalt | |---|---| | `test_app_integration.py` | **Objekt erstellen** (Klick, Ziehen aus der Sidebar, Abbruch), **greifen**, ziehen, **werfen**, Stillhalten ohne Wurf, gezogener Körper schiebt andere, bleibt in der Welt; **löschen** (Rechtsklick, Entf, Alles löschen); Pause, Schwerkraft, Regler (Klick, Ziehen, Mausrad), Tastenkürzel, Fenster schließen, Debug-Ansicht, Screenshot, Kommandozeile | | `test_widgets.py` | Regler-Abbildung und Rasterung, deaktivierte Regler, Umschalter, Farbwahl, Zeichnen aller Widgets, Viewport, Winkel-Interpolation | | `test_entry_points_and_edge_cases.py` | `python -m sandbox`, CLI ohne Optionen, Ende der Hauptschleife, Randfälle der Eingabe, Schrift-Fallback | | `test_smoke.py` | **Smoke-Test:** Die echte Hauptschleife läuft 3 s headless, während ein zufällig klickender "Affe" Klicks, Ziehen, Rechtsklicks, Mausrad und Tasten sendet – ohne Exception, danach ein gültiger Zustand | ### Architektur (`tests/test_architecture.py`) - Die Physik-Module und `config.py` importieren nur erlaubte Module (statische Prüfung über den Syntaxbaum). - `import sandbox.physics` lädt in einem frischen Interpreter kein pygame. ## Testabdeckung Stand der letzten Messung: **219 Tests, 100 % Zeilen- und Branch-Abdeckung** über das ganze Paket `sandbox` (inklusive UI), davon 100 % für `sandbox.physics`. Die hohe Abdeckung ist kein Selbstzweck. Entscheidend sind die physikalischen Erwartungswerte: Ein Test, der nur Code ausführt, ohne das Ergebnis gegen die Physik zu prüfen, wäre wertlos.