docs(adr): add ADR-001 for PHPUnit integration (#65, #67)
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

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
This commit is contained in:
Hermes
2026-06-04 07:47:09 +00:00
parent b5073fd892
commit f9295a2d07

View File

@@ -0,0 +1,184 @@
# 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.