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:
184
docs/adr/001-phpunit-integration.md
Normal file
184
docs/adr/001-phpunit-integration.md
Normal 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.
|
||||||
Reference in New Issue
Block a user