3 Tests
Ralf Warmuth edited this page 2026-01-09 22:32:38 +01:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Tests

Diese Seite dokumentiert die implementierten Tests im Code-Repo (zgb_www/tests/) ausführlich: Aufbau, Ausführung, Abdeckung und typische Stolperstellen.

Überblick: Welche Arten von Tests gibt es?

Das Projekt nutzt kein PHPUnit. Stattdessen gibt es:

  • Integrationstests (Runner): zgb_www/tests/run-tests.php
    • prüft zentrale Repository- und Service-Funktionen gegen eine echte SQLite-Testdatenbank
  • Assertion-Selbsttest: zgb_www/tests/assertion-validation.php
    • prüft, ob die im Runner verwendeten assert*()-Hilfsfunktionen korrekt arbeiten
  • Test-Validierung (Mutationstests): zgb_www/tests/validate-tests.php
    • Ziel: prüfen, ob Tests Code-Änderungen wirklich erkennen
    • Achtung: verändert temporär Quellcode-Dateien (mit Backup/Restore)
    • verwendet tests/bootstrap.php (shared bootstrap, keine PHPUnit-Abhängigkeit)

Zusätzliche Dokumente:

  • zgb_www/tests/TEST-VALIDATION-REPORT.md: zusammenfassender Bericht zur Test-Qualität/Abdeckung
  • zgb_www/tests/test-review.md: manuelle Review-Notizen zu den Tests

Voraussetzungen

Für die Tests wird benötigt:

  • PHP 8+
  • Extension pdo_sqlite
  • Schreibrechte im Repo, da Test-DBs/Backups erzeugt werden:
    • zgb_www/test_data/ (Test-DB Dateien)
    • zgb_www/test_backups/ (Backups für Mutationstests)

Tests ausführen

1) Hauptsuite: run-tests.php

Aus dem Repo-Root:

php tests/run-tests.php

Verhalten:

  • gibt pro Test (PASS) oder (FAIL) aus
  • Exit-Code:
    • 0 wenn alle Tests bestehen
    • 1 wenn mindestens ein Test fehlschlägt

Standardmäßig führt der Runner nach den Integrationstests auch Meta-Tests aus:

  • Assertion-Validierung (assertion-validation.php)
  • Mutationstests (validate-tests.php)

Das erhöht die Laufzeit, aber verbessert die Aussagekraft (“testen die Tests wirklich?”).

Technik:

  • jeder Test erzeugt eine eigene SQLite-Datei in test_data/ (uniqid() im Dateinamen)
  • die Datei wird am Ende (best effort) gelöscht
  • Session wird im CLI-Kontext so konfiguriert, dass es keine Cookie-Warnungen gibt

2) Assertion-Selbsttest: assertion-validation.php

php tests/assertion-validation.php

Das Script führt negative Tests aus (z.B. assertTrue(false) muss eine Exception werfen) und beendet sich mit:

  • 0 wenn alle Assertions korrekt funktionieren
  • 1 wenn eine Assertion “falsch” implementiert wäre

3) Mutationstests: validate-tests.php (Meta-Test)

Zweck: Absicherung, dass Tests nicht “grün” sind, obwohl die Software kaputt ist.
Dazu wird Code absichtlich manipuliert (“Mutation”), danach wird run-tests.php ausgeführt und es wird erwartet, dass die Tests fehlschlagen.

Wenn/ sobald lauffähig, gilt:

  • Es erzeugt Backups in test_backups/ und stellt sie nach jedem Mutationsdurchlauf wieder her.
  • Es führt u.a. diese Mutationen durch:
    • ConfigRepository::loadConfig() liefert absichtlich falsches startDate
    • RegistrationRepository::saveRegistration() speichert “nicht”
    • AuthService::login() setzt is_admin nicht

Warnung: Mutationstests ändern Dateien in zgb-backend/ temporär.
Nicht auf einem Shared-Checkout laufen lassen und nicht parallel zu anderen Prozessen.

Meta-Tests gezielt überspringen

Wenn du nur die Integrationstests laufen lassen willst (z.B. wenn du selbst gerade an validate-tests.php arbeitest), kannst du Meta-Tests überspringen:

php tests/run-tests.php --skip-meta

Hinweis: validate-tests.php nutzt intern ebenfalls --skip-meta, um Rekursion zu vermeiden.

Was wird getestet? (Abdeckung im Detail)

Die folgenden Punkte beziehen sich auf tests/run-tests.php.

ConfigRepository

Abgedeckt:

  • loadConfig() liefert ein Config-Objekt und hat Default-Werte
  • saveConfig() persistiert Werte, die wieder geladen werden können
  • setConfig() / getConfig() für einzelne Keys (inkl. “Key not found” Exception)
  • pdo() Getter gibt die verwendete PDO-Instanz zurück

Warum relevant:

  • Config ist zentral (Anmeldezeitraum, Nummernrange, operatorEmail, eventDate, Admin-Passwort-Hash, Mail-Queue-Keys)

RegistrationRepository

Abgedeckt:

  • saveRegistration() + getAllRegistrations() Roundtrip
  • loadRegistration(id) liefert Objekt oder null (nicht existierende ID)
  • UNIQUE-Constraint: doppelte number wird verhindert (PDOException)
  • existsDuplicateRegistration() erkennt Duplikate auf Kerndaten (und erkennt Nicht-Duplikate)
  • teamMember wird korrekt gespeichert/geladen
  • Validierungsfunktionen:
    • isNumberValidAndAvailable(number, start, end) (Bereich + Verfügbarkeit)
    • getFirstFreeNumber(start, end) liefert null bei vollem Kontingent
    • Verhalten bei “Lücken” in der Nummernfolge

Warum relevant:

  • Nummernlogik und Duplikatlogik sind kritisch für die Anmeldung unter Last.

AuthService (Admin-Auth)

Abgedeckt:

  • login() / logout() / isAdmin() (Session-Flag)
  • Rate-Limit: failRateLimit() erhöht counter, resetRateLimit() setzt zurück

Einschränkung:

  • Funktionen, die exit() ausführen (z.B. requireAdmin() oder checkRateLimit() im Block-Fall), sind nur indirekt testbar; der Test prüft deshalb v.a. Voraussetzungen und Session-State.

CSRFService

Abgedeckt:

  • getToken() erzeugt Token (64 hex chars = 32 bytes)
  • Token bleibt innerhalb TTL gleich
  • checkToken(token) akzeptiert gültige Tokens und wirft bei ungültigen
  • rotateToken() erzeugt neuen Token
  • TTL-Ablauf: Session-Zeitstempel wird im Test künstlich “alt” gesetzt und geprüft, dass neuer Token entsteht

Warum relevant:

  • Admin-POSTs und Registrierungs-POST sind CSRF-geschützt; Token-/TTL-Verhalten ist sicherheitskritisch.

MailQueueRepository (Queue-Mechanik)

Abgedeckt:

  • enqueue() ist idempotent (doppeltes Enqueue → kein doppelter Job)
  • claimNextJobs() claimt Jobs und deleteJob() räumt erfolgreich gesendete Jobs weg (“Option A”)
  • recoverStuckSending() setzt Jobs nach abgelaufenem Lock wieder zurück

Hinweis:

  • Der Worker (MailQueueWorker) selbst wird im Runner nicht als End-to-End “send loop” getestet (kein echter Mailversand). Die wichtigsten Datenbank-Operationen sind aber abgedeckt.

MailService (Templates, ohne echte Mails)

Abgedeckt:

  • Template-Datei existiert und enthält Platzhalter
  • einfache Platzhalter-Ersetzung im Test (ohne echten mail() Aufruf)

Warum so:

  • Echten Mailversand zu testen ist ohne Mock/Abstraktion schwierig und in CI meist unerwünscht.

Counter-Logik (fachliche Berechnungen)

Abgedeckt:

  • “ohne Registrierungen”
  • “mit vielen Registrierungen”
  • “volles Kontingent”

Hinweis:

  • Das ist eine Logikprüfung innerhalb des Tests (keine direkte Ausführung von counter.php als HTTP-Request).

Repository::CreatePDO (Edge Cases)

Abgedeckt:

  • Verhalten bei “ungültigem Pfad” ist OS-abhängig (Windows kann Verzeichnisse ggf. erzeugen). Der Test akzeptiert daher entweder “funktioniert” oder “wirft eine sinnvolle Exception”.

Artefakte / Cleanup

Die Tests erzeugen Dateien:

  • zgb_www/test_data/test_*.db (sollten nach jedem Test gelöscht werden)
  • zgb_www/test_backups/*.backup (für Mutationstests)

Wenn Tests hart abbrechen (z.B. Ctrl+C), können Dateien liegen bleiben dann einfach den Ordner test_data/ bereinigen.

Troubleshooting

  • pdo_sqlite fehlt: Runner warnt; DB-Tests werden fehlschlagen.
  • DB-Datei lässt sich nicht löschen (v.a. Windows): Prozess hält ggf. noch einen Handle.
    Tipp: Testlauf beenden, kurz warten, erneut probieren; ggf. prüfen, ob ein PHP-Prozess noch läuft.
  • Session-Warnungen: Der Runner nutzt Output-Buffering und setzt Session-Inis für CLI; falls dennoch Warnungen erscheinen, ist oft eine PHP-Konfiguration ungewöhnlich.

Empfehlungen für Erweiterungen (für Contributors)

  • Neue Tests bevorzugt als zusätzliche runTest('Name', function() { ... }) Blöcke in run-tests.php, isoliert mit eigener Test-DB.
  • Keine externen Side-Effects (keine echten Mails, keine echten Netzwerkzugriffe).
  • Bei Code, der exit() aufruft: entweder Zustand vor exit() prüfen oder (optional) den Code refactoren, um testbarer zu werden (z.B. Exceptions statt exit() in Kernlogik).