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
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:
- Refactoring-Risiko: Ohne CI-Tests können Bugs bei zukünftigen Änderungen unentdeckt auf
mainlanden - Regressions: Kein Schutz gegen versehentliches Brechen existierender Tests
- Code-Qualität: Manuelle Test-Verifikation ist fehleranfällig (vergessen, übersprungen)
- 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_requestaufmain - Runtime:
ubuntu-latest, PHP 8.5 + Composer - Install:
apt-get install -y php-cli composer php-xml php-mbstring - Test:
composer install(Lazy: nur wennvendor/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.shausführen - Bei Non-PHP-Commits: PHPUnit skippen (Performance)
- Bei Test-Fehler: Exit-Code != 0 → Husky bricht Commit ab
- DRY: Shared
scripts/pre-commit-checks.shwird auch vonscripts/safe-commit.shaufgerufen (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 commitstatt 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/) mussphpunit.xmlggf. 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/wirdcomposer installausgefü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.shruftpre-commit-checks.shdirekt auf (auch bei--no-verifywürde der Bypass hier nicht greifen, dasafe-commit.shdas Script direkt invoked)
Risiken
- PHP-Versions-Drift: CI läuft auf PHP 8.5, lokal möglicherweise älter. Mitigation:
phpunit.xmlschema-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.lockmuss gepflegt sein - Stale-Index-Edge-Case:
git add file.php; rm file.php; git commitwürde PHPUnit gegen veraltete staged-Version laufen lassen. Mitigation: Stale-Index-Safety-Check inpre-commit-checks.shprü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-verifyirrelevant) - 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/phpunitist 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-PatternApp\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.ymllöschen → keine CI-Tests mehr - Pre-Commit:
.husky/pre-commitrevertieren +scripts/pre-commit-checks.shlö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:
- Die Architektur-Entscheidung für die Nachwelt festzuhalten
- Den bewussten 2-Layer-Ansatz (lokal + CI) zu begründen
- 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.