Files
landingpage-haus-schleusingen/docs/adr/001-phpunit-integration.md
Hermes f9295a2d07
All checks were successful
Lint / PHP Syntax Check (push) Successful in 56s
PHPUnit / PHP Unit Tests (push) Successful in 1m7s
Lint / HTML Lint (htmlhint) (push) Successful in 1m36s
Lint / CSS Lint (stylelint) (push) Successful in 1m40s
docs(adr): add ADR-001 for PHPUnit integration (#65, #67)
Nachtraegliche Architektur-Dokumentation der Test-Integration
(CI-Pipeline + Pre-Commit-Hook mit Shared-Script-Pattern).

- Begruendet 2-Layer-Strategie (lokal + CI)
- Dokumentiert Performance-Optimierungen (Conditional PHPUnit, Composer-Lazy-Install)
- Listet verworfene Alternativen mit Rationale
- Beschreibt Stale-Index-Edge-Case-Mitigation
2026-06-04 07:47:09 +00:00

8.9 KiB

ADR-001: PHPUnit-Integration in CI + Pre-Commit-Hook

Status: Accepted (nachträglich dokumentiert) Datum: 2026-06-04 Issues: #65, #67 PRs: #69, #70 Author: Hermes (nachträgliche Doku auf Martin-Anweisung)

Kontext

Das Projekt landingpage-haus-schleusingen enthält 18 PHPUnit-Tests (31 Assertions, 100% Pass-Rate) im tests/-Verzeichnis. Vor diesem ADR gab es:

  • Lokale Test-Verifikation nur manuell (vendor/bin/phpunit)
  • Keine CI-Pipeline — Tests liefen nicht automatisch bei Push/PR
  • Pre-Commit-Hook deckte nur Linting (PHP-Syntax, HTML, CSS, JS, Prettier), keine Tests

Probleme:

  1. Refactoring-Risiko: Ohne CI-Tests können Bugs bei zukünftigen Änderungen unentdeckt auf main landen
  2. Regressions: Kein Schutz gegen versehentliches Brechen existierender Tests
  3. Code-Qualität: Manuelle Test-Verifikation ist fehleranfällig (vergessen, übersprungen)
  4. Reviewer-Belastung: Martin muss bei jedem PR manuell Tests laufen lassen

Anforderungen:

  • Tests müssen automatisch bei jedem Push/PR laufen
  • Tests müssen lokal vor dem Commit laufen (schneller Feedback-Loop)
  • Bei Test-Fehler: Commit/PR abbrechen mit klarer Fehlermeldung
  • Performance: Tests dürfen nicht bei CSS/HTML/JS-only-Änderungen laufen (false-positive-Friktion vermeiden)
  • DRY: Eine einzige Test-Ausführungslogik für lokale + CI-Ausführung

Entscheidung

Zwei-Layer-Strategie: CI-Pipeline (Remote-Verifikation) + Pre-Commit-Hook (Lokal vor Push).

Layer 1: CI-Pipeline (.gitea/workflows/phpunit.yml)

  • Trigger: push + pull_request auf main
  • Runtime: ubuntu-latest, PHP 8.5 + Composer
  • Install: apt-get install -y php-cli composer php-xml php-mbstring
  • Test: composer install (Lazy: nur wenn vendor/ fehlt) → vendor/bin/phpunit
  • Architektur: Analog zu existierender lint.yml, eigenständige Pipeline (nicht mit Lint kombiniert, da Test-Laufzeit ~25s unabhängig von Lint)

Layer 2: Pre-Commit-Hook (.husky/pre-commit + scripts/pre-commit-checks.sh)

  • Trigger: Lokaler git commit (Husky 9 Standard)
  • PHP_Detection: git diff --cached --name-only | grep -E '\.(php)$|^phpunit\.xml$|^composer\.(json|lock)$'
  • Bei PHP-Files: scripts/pre-commit-checks.sh ausführen
  • Bei Non-PHP-Commits: PHPUnit skippen (Performance)
  • Bei Test-Fehler: Exit-Code != 0 → Husky bricht Commit ab
  • DRY: Shared scripts/pre-commit-checks.sh wird auch von scripts/safe-commit.sh aufgerufen (AI-Agent-Bypass-Schutz)

Schichten-Logik

┌─────────────────────────────────────┐
│ Git Commit (lokal)                  │
│   ↓                                 │
│ Husky Pre-Commit Hook               │
│   ↓                                 │
│ scripts/pre-commit-checks.sh        │ ← Eine Source-of-Truth
│   ├─ Lint (PHP, HTML, CSS, JS)      │
│   └─ PHPUnit (wenn PHP touched)     │
│       ├─ composer install (lazy)    │
│       └─ vendor/bin/phpunit         │
│   ↓ (Exit 0)                        │
│ Commit erstellt                     │
│   ↓                                 │
│ git push → Gitea                    │
│   ↓                                 │
│ .gitea/workflows/phpunit.yml        │ ← CI-Verifikation
│   ├─ PHP + Composer install         │
│   └─ vendor/bin/phpunit             │
│   ↓ (Exit 0)                        │
│ PR mergeable                        │
└─────────────────────────────────────┘

Konsequenzen

Positiv

  • Doppelte Absicherung: Lokal (schnell) + CI (authoritativ)
  • Frühes Feedback: Entwickler merkt sofort bei git commit statt erst nach Push
  • Performance: Non-PHP-Commits (CSS, HTML, JS, Markdown) lösen keinen PHPUnit-Run aus
  • Wartbarkeit: Single-Source-of-Truth (scripts/pre-commit-checks.sh) — Hook und safe-commit.sh synchron
  • CI-Laufzeit: ~25s für 18 Tests, akzeptabel für Standard-Pipeline
  • Audit-Kette: Issue → PR → Merge → autom. Issue-Close bleibt sauber

Negativ

  • Wartungs-Overhead: Bei neuen Test-Dateien (z.B. tests/Integration/) muss phpunit.xml ggf. angepasst werden
  • Pre-Commit-Delay: Bei PHP-Commits ~5-10s lokaler Test-Lauf (akzeptabel, schneller als CI-Round-Trip)
  • Composer-Install-Falle: Bei fehlendem vendor/ wird composer install ausgeführt — potenziell langsam beim ersten Commit in neuem Clone
  • Bypass-Pfad: git commit --no-verify überspringt Hook (per Design, aber Risiko)
  • Schutz gegen Bypass: scripts/safe-commit.sh ruft pre-commit-checks.sh direkt auf (auch bei --no-verify würde der Bypass hier nicht greifen, da safe-commit.sh das Script direkt invoked)

Risiken

  • PHP-Versions-Drift: CI läuft auf PHP 8.5, lokal möglicherweise älter. Mitigation: phpunit.xml schema-konform, keine PHP-8.5-spezifischen Features in Tests
  • Test-Datenbank: Aktuell keine DB-Tests, aber bei zukünftigen Integration-Tests muss SQLite-in-memory oder Test-Fixture sichergestellt werden
  • Composer-Versions-Drift: CI nutzt neueste Composer-Version, lokal ggf. älter → composer.lock muss gepflegt sein
  • Stale-Index-Edge-Case: git add file.php; rm file.php; git commit würde PHPUnit gegen veraltete staged-Version laufen lassen. Mitigation: Stale-Index-Safety-Check in pre-commit-checks.sh prüft Disk-Existenz aller gestaged PHP-Files

Alternativen (verworfen)

Alternative A: PHPUnit NUR in CI, kein Pre-Commit-Hook

  • Pro: Einfacher, kein lokaler Overhead
  • Pro: Bypass unmöglich (--no-verify irrelevant)
  • Contra: Feedback-Loop erst nach Push (30s+)
  • Contra: Martin muss auf CI warten statt sofort beim Commit zu sehen
  • Verworfen weil: Schnelleres Feedback-Loop wichtiger als Einfachheit

Alternative B: PHPUnit NUR lokal, keine CI-Pipeline

  • Pro: Schnellste lokale Feedback-Loop
  • Pro: Keine CI-Infrastruktur nötig
  • Contra: Kein Schutz vor git commit --no-verify-Bypass
  • Contra: Kein Schutz vor ungetesteten Pushes direkt auf main
  • Verworfen weil: CI-Protection vor versehentlichen Pushes essentiell

Alternative C: PHPUnit mit npm test statt direkter vendor/bin/phpunit

  • Pro: Konsistenz mit existierendem npm run lint-Pattern
  • Pro: Lint + Test in einem Schritt möglich
  • Contra: Zusätzlicher npm-Wrapper-Layer, Overhead
  • Contra: PHP-Files würden trotzdem in npm-Skript laufen (unidiomatisch)
  • Verworfen weil: Direkter vendor/bin/phpunit ist PHP-idiomatisch, klarer

Alternative D: PHPUnit in bestehende lint.yml integrieren

  • Pro: Weniger Workflow-Files
  • Contra: Lint- und Test-Stage schwerer zu trennen
  • Contra: Lint-Pipeline bricht bei Test-Fehler, obwohl Lint sauber ist
  • Verworfen weil: Trennung der Verantwortlichkeiten (Lint = Syntax, Test = Verhalten)

Auswirkungen

Performance

  • CI-Pipeline: ~25s für 18 Tests, akzeptabel
  • Pre-Commit-Local: ~5-10s bei PHP-Commits, <1s bei Non-PHP-Commits (Skip)
  • Composer-Install: ~3-5s beim ersten Run nach Clone, dann Cache-Hit

Security

  • Keine Secrets in Test-Files
  • Keine externen API-Calls in Tests
  • Keine Production-DB-Zugriffe (alle Tests in-memory oder mit Test-Fixtures)

Testbarkeit

  • Bestehende Tests bleiben unverändert (TDD-konform: 18 Tests, 31 Assertions)
  • PHPUnit-Konfiguration in phpunit.xml
  • Test-Layout: tests/Core/RouterTest.php (Namespaces-Pattern App\Tests\)

Migrationspfad

  • Keine Migration nötig — additive Änderung
  • Bestehende Commits funktionieren unverändert
  • Hook aktiviert sich automatisch bei npm install (Husky 9 Standard)
  • CI-Pipeline triggert beim ersten Push nach Merge

Rollback

  • CI: .gitea/workflows/phpunit.yml löschen → keine CI-Tests mehr
  • Pre-Commit: .husky/pre-commit revertieren + scripts/pre-commit-checks.sh löschen
  • Atomic: Jede Schicht unabhängig deaktivierbar

Verwandte Entscheidungen

  • ADR-002 (offen): Stylelint-Pattern-Fix für ./public/css/**/*.css (Folge-Bug entdeckt beim Test der Pipeline)
  • ADR-003 (offen): act_runner-Docker-Orchestrierung-Workaround (v0.6.1 startet keine Container)

Nachträgliche Dokumentation

Dieser ADR wird nachträglich erstellt (Code-Phase bereits abgeschlossen, PRs gemerged), um:

  1. Die Architektur-Entscheidung für die Nachwelt festzuhalten
  2. Den bewussten 2-Layer-Ansatz (lokal + CI) zu begründen
  3. Als Template für zukünftige Test-Integrationen in anderen Projekten zu dienen

Lesson Learned: ADRs sollten VOR der Code-Phase erstellt werden (Forward-Engineering). Nachträgliche Doku ist besser als keine, aber ein Auditor würde die Entscheidungs-Spur zwischen Issue-Erstellung und Implementation schwer nachvollziehen können. Für die nächsten Issues: ADR in Ph0.5 verbindlich, nicht erst in Ph8.