Testprinzipien
Sprachspezifische Referenzen
Für sprachspezifische Testmuster lesen Sie bitte auch:
- TypeScript (Vitest/RTL/Playwright): references/typescript.md
Grundlegende Testphilosophie
- Tests sind Code erster Klasse: Halten Sie die Testqualität auf dem Niveau des Produktionscodes.
- Schnelles Feedback: Tests sollten schnell ausgeführt werden und sofortiges Feedback geben.
- Zuverlässigkeit: Tests sollten deterministisch und reproduzierbar sein.
- Unabhängigkeit: Jeder Test sollte isoliert ausgeführt werden.
TDD-Prozess [VERPFLICHTEND für alle Codeänderungen]
Führen Sie diesen Prozess für jede Codeänderung aus:
RED-Phase
SCHRITT 1: Schreiben Sie einen Test, der das erwartete Verhalten definiert. SCHRITT 2: Führen Sie den Test aus. SCHRITT 3: Bestätigen Sie, dass der Test FEHLSCHLÄGT (wenn er besteht, ist der Test falsch).
GREEN-Phase
SCHRITT 1: Schreiben Sie MINIMALEN Code, um den Test zu bestehen. SCHRITT 2: Führen Sie den Test aus. SCHRITT 3: Bestätigen Sie, dass der Test BESTEHT.
REFACTOR-Phase
SCHRITT 1: Verbessern Sie die Codequalität (Doppelungen entfernen, Namensgebung verbessern). SCHRITT 2: Führen Sie den Test aus. SCHRITT 3: Bestätigen Sie, dass der Test IMMER NOCH BESTEHT.
VERIFY-Phase [VERPFLICHTEND – 0 FEHLER ERFORDERLICH]
SCHRITT 1: Führen Sie ALLE Qualitätsprüfungsbefehle für Ihre Sprache/Ihr Projekt aus. SCHRITT 2: Beheben Sie alle Fehler, bis ALLE Befehle mit 0 Fehlern bestehen. SCHRITT 3: Bestätigen Sie, dass keine Regressionen aufgetreten sind.
DURCHSETZUNG: Bei Fehlern in einer Qualitätsprüfung darf nicht zur nächsten Phase übergegangen werden.
TDD-Ausnahmen (kein TDD erforderlich)
- Reine Konfigurationsdateien
- Nur Dokumentation
- Notfallbehebungen (aber sofort nach der Behebung Tests hinzufügen)
- Explorative Spikes (vor dem Zusammenführen verwerfen oder mit Tests neu schreiben)
- Build-/Deployment-Skripte (sofern sie keine Geschäftslogik enthalten)
Qualitätsanforderungen [VERPFLICHTEND]
Abdeckung
- Behandeln Sie die Abdeckung als diagnostisches Signal, um ungetestete Bereiche zu finden, nicht als Ziel. Ziele werden durch triviale Tests umgangen.
- Konzentrieren Sie Tests auf kritische Pfade, Geschäftslogik und Verhalten, dessen Regression wichtig wäre.
- Priorisieren Sie aussagekräftige Assertions gegenüber der Abdeckungszahl. Jeder erforderliche Schwellenwert stammt aus der CI des Projekts, der Aufgabendatei, dem Arbeitsplan oder dem Design Doc.
Testeigenschaften
Alle Tests MÜSSEN:
- Unabhängig: Keine Abhängigkeiten zwischen Tests.
- Reproduzierbar: Gleiche Eingabe erzeugt immer gleiche Ausgabe.
- Schnell: Die gesamte Testsammlung innerhalb des akzeptierten Feedback-Fensters des Projekts abschließen und Suiten kennzeichnen, die die lokale Iteration oder CI wesentlich verlangsamen.
- Selbstprüfend: Klar bestanden/nicht bestanden ohne manuelle Überprüfung.
- Rechtzeitig: Kurz vor dem zu testenden Code geschrieben.
DURCHSETZUNG: Tests, die bei IRGENDEINER Eigenschaft fehlschlagen, MÜSSEN sofort behoben werden.
Testarten
Unit-Tests
Zweck: Testen einzelner Komponenten in Isolation.
Eigenschaften:
- Testen einer einzelnen Funktion, Methode oder Klasse.
- Schnelle Ausführung (Millisekunden).
- Keine externen Abhängigkeiten.
- Externe Dienste werden gemockt.
- Bilden den Großteil Ihrer Testsammlung.
Integrationstests
Zweck: Testen der Interaktionen zwischen Komponenten.
Eigenschaften:
- Testen mehrerer Komponenten zusammen.
- Können Datenbank, Dateisystem oder APIs umfassen.
- Langsamer als Unit-Tests.
- Überprüfen Verträge zwischen Modulen.
- Bilden einen kleineren Teil der Testsammlung.
End-to-End (E2E) Tests
Zweck: Testen kompletter Arbeitsabläufe aus Benutzersicht.
Eigenschaften:
- Testen den gesamten Anwendungsstapel.
- Simulieren reale Benutzerinteraktionen.
- Langsamste Testart.
- Geringste Anzahl.
- Höchstes Vertrauensniveau.
Testpyramide
Folgen Sie der Testpyramiden-Struktur:
/\ <- Wenige E2E-Tests (Hohes Vertrauen, langsam)
/ \
/ \ <- Einige Integrationstests (Mittleres Vertrauen, mittlere Geschwindigkeit)
/ \
/________\ <- Viele Unit-Tests (Schnell, grundlegend)
Testdesign-Prinzipien
AAA-Muster (Arrange-Act-Assert)
Strukturieren Sie jeden Test in drei klaren Phasen:
// Arrange: Testdaten und -bedingungen einrichten
user = createTestUser()
validator = createValidator()
// Act: Den zu testenden Code ausführen
result = validator.validate(user)
// Assert: Erwartetes Ergebnis überprüfen
assert(result.isValid == true)
Eine Assertion pro Konzept
- Testen Sie ein Verhalten pro Testfall.
- Mehrere Assertions sind in Ordnung, wenn ein einzelnes Konzept getestet wird.
- Teilen Sie nicht zusammenhängende Assertions in separate Tests auf – ein Test MUSS genau ein Verhalten überprüfen.
Beschreibende Testnamen
Testnamen sollten klar beschreiben:
- Was wird getestet?
- Unter welchen Bedingungen?
- Was ist das erwartete Ergebnis?
Empfohlenes Format: "sollte [erwartetes Verhalten] wenn [Bedingung]"
Testunabhängigkeit
Isolationsanforderungen
- Jeder Test erstellt seine eigenen Testdaten.
- Keine Abhängigkeiten von der Ausführungsreihenfolge.
- Eigener Zustand wird bereinigt.
- Bestehen, wenn sie isoliert ausgeführt werden.
Setup und Teardown
- Verwenden Sie Setup-Hooks, um die Testumgebung vorzubereiten.
- Verwenden Sie Teardown-Hooks, um Ressourcen zu bereinigen.
- Halten Sie das Setup auf die Daten, Abhängigkeiten und Fixtures beschränkt, die für das zu testende Verhalten erforderlich sind.
- Stellen Sie sicher, dass Teardown auch ausgeführt wird, wenn der Test fehlschlägt.
Mocking und Test-Doubles
Wann Mocks verwendet werden sollten
- Externe Abhängigkeiten mocken: APIs, Datenbanken, Dateisysteme.
- Langsame Operationen mocken: Netzwerkaufrufe, aufwändige Berechnungen.
- Unvorhersehbares Verhalten mocken: Zufallswerte, aktuelle Uhrzeit.
- Nicht verfügbare Dienste mocken: Drittanbieterdienste.
Mocking-Prinzipien [VERPFLICHTEND]
- An Grenzen mocken, nicht intern – für interne Hilfsfunktionen echte Implementierungen verwenden.
- Jeden Mock auf das Verhalten beschränken, das der Test steuern oder beobachten muss.
- Mock-Erwartungen bei Bedarf überprüfen.
- Adapter für externe Bibliotheken/Frameworks verwenden, die Sie nicht kontrollieren.
Arten von Test-Doubles
- Stub: Liefert vorbestimmte Werte zurück.
- Mock: Überprüft, ob es korrekt aufgerufen wurde.
- Spy: Zeichnet Informationen über Aufrufe auf.
- Fake: Vereinfachte, funktionierende Implementierung.
- Dummy: Wird übergeben, aber niemals verwendet.
Testen der Datenschicht
Mock-Einschränkungen für Datenzugriff
Mocks validieren Aufrufmuster, aber nicht Schema-Korrektheit, Abfragekorrektheit oder Speicherbeschränkungen. Beispiele für Probleme, die Mocks übersehen können:
- Schema-Drift.
- Spalten- oder Feldinkonsistenzen.
- Falsche Joins, Filter oder Aggregationen.
- Migrationsinkompatibilität.
Wann die Überprüfung der echten Datenschicht Mehrwert bietet
Verwenden Sie echte oder produktionsähnliche Datenzugriffsüberprüfungen beim Testen von:
- Repository- oder DAO-Implementierungen.
- ORM-Mappings.
- Abfrage-Builder oder rohem SQL.
- Persistenzverhalten, das von Constraints oder Schema-Form abhängt.
Umgebungsoptionen
Wählen Sie die praktischste Option für die Projektumgebung:
- Containerisierte Datenbank.
- Dedizierte Testdatenbank.
- In-Memory-Datenbank mit dokumentierten Einschränkungen.
- Adaptergestütztes lokales Test-Harness.
Design-Ausrichtung
Wenn ein Design Doc „Test Boundaries“ enthält, folgen Sie ihm als Grundlage für die Entscheidung, welche Abhängigkeiten real bleiben und welche Grenzen isoliert werden.
Testqualitätspraktiken [VERPFLICHTEND]
Tests aktiv halten
- Fehlschlagende Tests beheben oder löschen: Fehler sofort beheben.
- Auskommentierte Tests entfernen: Beheben oder vollständig löschen.
- Tests laufen lassen: Kaputte Tests verlieren schnell an Wert.
- Testsammlung pflegen: Tests bei Bedarf umgestalten.
Testcode-Qualität
- Gleiche Standards wie für Produktionscode anwenden.
- Beschreibende Variablennamen verwenden.
- Test-Helfer extrahieren, um Doppelungen zu reduzieren.
- Tests lesbar und wartbar halten.
Test-Helfer und Dienstprogramme
- Wiederverwendbare Testdaten-Builder erstellen.
- Gemeinsames Setup in Helper-Funktionen extrahieren.
- Test-Utilities für komplexe Szenarien erstellen.
- Helfer je nach Bedarf über Testdateien hinweg teilen.
Was getestet werden sollte
Fokus auf Verhalten
Testen Sie beobachtbares Verhalten, nicht die Implementierung:
- Gut: Testen, ob eine Funktion die erwartete Ausgabe zurückgibt.
- Gut: Testen, ob der richtige API-Endpunkt aufgerufen wird.
- Schlecht: Testen, ob eine interne Variable gesetzt wurde.
- Schlecht: Testen der Reihenfolge privater Methodenaufrufe.
Grenzfälle testen
Testen Sie immer:
- Randbedingungen: Min-/Max-Werte, leere Sammlungen.
- Fehlerfälle: Ungültige Eingabe, Nullwerte, fehlende Daten.
- Extremfälle: Sonderzeichen, extreme Werte.
- Erfolgspfad: Normale, erwartete Nutzung.
Testqualitätskriterien [VERPFLICHTEND]
- Wörtliche Erwartungen: Verwenden Sie hartcodierte Literalwerte in Assertions – erwarteter Wert ≠ Mock-Rückgabewert (Implementierung verarbeitet Daten).
- Ergebnisüberprüfung: Assertieren Sie Rückgabewerte und Zustand, nicht die Aufrufreihenfolge.
- Aussagekräftige Assertions: Jeder Test MUSS mindestens eine Assertion haben – ein Test ohne Assertions bietet keinen Wert.
- Nur externes I/O mocken: Mocken Sie DB/API/Dateisystem, verwenden Sie echte interne Hilfsfunktionen.
- Randabdeckung: Leere/Null/Max/Fehlerfälle zusammen mit Erfolgspfaden einschließen.
DURCHSETZUNG: Tests, die IRGENDEIN Kriterium verletzen, MÜSSEN neu geschrieben werden.
Überprüfungsanforderungen [VERPFLICHTEND für die VERIFY-Phase]
Checkliste vor dem Commit
☐ Alle Tests bestehen. ☐ Keine Tests übersprungen oder auskommentiert. ☐ Kein Debug-Code in Tests zurückgelassen. ☐ Abdeckungsschwellenwert bestanden, wenn das Projekt, die Aufgabendatei, der Arbeitsplan oder das Design Doc einen definieren. ☐ Tests laufen in angemessener Zeit.
Null-Toleranz-Politik
- Null fehlschlagende Tests: Sofort beheben.
- Null übersprungene Tests: Löschen oder beheben.
- Null flaky Tests: Deterministisch machen.
- Null langsame Tests: Optimieren oder aufteilen.
DURCHSETZUNG: Kann nicht mit der Aufgabenerledigung fortfahren, wenn IRGENDEINE Qualitätsprüfung fehlschlägt.
Testorganisation
Dateistruktur
- Produktionsstruktur spiegeln: Tests folgen der Code-Organisation.
- Klare Namenskonventionen: Den Testdateimustern des Projekts folgen.
- Logische Gruppierung: Zusammengehörige Tests gruppieren.
- Testarten trennen: Unit-, Integrations- und E2E-Tests in separaten Verzeichnissen.
Testsammlungs-Organisation
tests/
├── unit/ # Schnelle, isolierte Unit-Tests
├── integration/ # Integrationstests
├── e2e/ # End-to-End-Tests
├── fixtures/ # Testdaten und Fixtures
└── helpers/ # Gemeinsam genutzte Test-Utilities
Leistungsüberlegungen
Testgeschwindigkeit
- Unit-Tests: < 100ms pro Test.
- Integrationstests: < 1s pro Test.
- Gesamtsuite: Sollte häufig ausgeführt werden können (< 10 Minuten).
Häufige Anti-Patterns
Erkennen und beseitigen Sie diese Muster sofort:
- Tests, die nichts testen (bestehen immer).
- Tests, die von der Ausführungsreihenfolge abhängen.
- Tests, die von externem Zustand abhängen.
- Tests mit komplexer Logik (Tests, die eigene Tests benötigen).
- Testen von Implementierungsdetails anstelle von beobachtbarem Verhalten.
- Übermäßiges Mocking (nur Grenzen mocken, echte Interna verwenden).
- Doppelung von Testcode.
Flaky Tests
Beseitigen Sie Tests, die zeitweise fehlschlagen:
- Timing-Abhängigkeiten entfernen.
- Deterministische Daten anstelle von Zufallswerten verwenden.
- Ordnungsgemäße Bereinigung sicherstellen.
- Race Conditions beheben.
- Alle Tests deterministisch machen.
Regressionstests
- Für jede Fehlerbehebung einen Test hinzufügen.
- Umfassende Testsammlung pflegen.
- Gesamtsuite regelmäßig ausführen.
- Einen Test nur löschen, wenn das abgedeckte Verhalten nicht mehr existiert oder das gleiche Verhalten durch einen stärkeren Test auf der richtigen Ebene abgedeckt wird.
Legacy-Code
- Charakterisierungstests vor dem Refactoring hinzufügen.
- Zuerst das bestehende Verhalten testen.
- Abdeckung schrittweise verbessern.
- Mit Vertrauen umgestalten.