Zum Hauptinhalt springen

🧪 Unit Testing

Unit Tests sind kein Selbstzweck.
Sie sind ein zentrales Qualitätsinstrument zur Reduktion technischer Risiken und zur Absicherung von Veränderbarkeit.

Professionelle Softwareentwicklung ist ohne systematische Tests langfristig nicht skalierbar.


1. Ziel und Einordnung

1.1 Was sind Unit Tests?

Unit Tests prüfen eine isolierte fachliche Einheit (Funktion, Methode, Klasse) unter kontrollierten Bedingungen.

Charakteristika:

  • Keine echte Infrastruktur
  • Keine echte Datenbank
  • Keine echten HTTP-Calls
  • Keine Framework-Initialisierung
  • Vollständig deterministisch

Eine Unit ist dabei nicht zwingend eine Klasse, sondern eine fachlich geschlossene Logikeinheit.


1.2 Wofür sind Unit Tests da?

Unit Tests dienen der:

  • Absicherung fachlicher Logik
  • Dokumentation von Verhalten
  • Prävention von Regressionen
  • Ermöglichung sicherer Refactorings
  • Rückmeldung über Testbarkeit (Design-Feedback)

Gut testbarer Code ist in der Regel besser geschnitten, entkoppelt und wartbar.


1.3 Was sind Unit Tests nicht?

Unit Tests sind:

  • kein Ersatz für Integrations- oder E2E-Tests
  • kein Beweis für Fehlerfreiheit
  • kein KPI-Instrument zur Erreichung von 100 % Coverage
  • keine Simulation realer Produktionsumgebungen

Sie prüfen Verhalten isoliert – nicht Systemintegration.


2. Test-Mindset

2.1 Qualität vor Coverage

100 % Line Coverage bedeutet nicht 100 % Qualität.

Ziel ist nicht maximale Coverage, sondern:

  • Vertrauen in kritische Logik
  • Reduktion fachlicher Risiken
  • Absicherung gegen unbeabsichtigte Änderungen

Branch Coverage ist aussagekräftiger als reine Line Coverage.

Kritische Domänenlogik sollte besonders sorgfältig getestet werden.


2.2 Teste Verhalten, nicht Implementierung

Tests beschreiben beobachtbares Verhalten – nicht interne Details.

Nicht testen:

  • private Methoden
  • interne Hilfsfunktionen
  • konkrete Framework-Mechaniken

Ein Refactoring darf Tests nicht brechen, solange sich das Verhalten nicht ändert.


2.3 Ein Test = ein klarer Grund zu scheitern

Ein Test sollte:

  • einen klar abgegrenzten Aspekt prüfen
  • bei Fehlern eindeutig diagnostisch sein

Mehrere Assertions sind erlaubt, wenn sie denselben Sachverhalt betreffen.


3. Test-Design-Prinzipien

3.1 AAA-Prinzip

  • Arrange
  • Act
  • Assert

Klare Struktur erhöht Lesbarkeit und Wartbarkeit.


3.2 Given–When–Then

Tests sind auch Dokumentation.

Beschreibe Verhalten aus fachlicher Perspektive, nicht aus technischer Sicht.


3.3 Keine Logik im Test

Tests sind keine zweite Implementierung.

Vermeiden:

  • komplexe Berechnungen
  • Schleifen
  • Verzweigungen
  • eigene Entscheidungslogik

Wenn der Test zu komplex wird, ist meist auch die getestete Einheit zu komplex.


3.4 Determinismus

Unit Tests müssen reproduzierbar sein.

Nicht erlaubt:

  • Zufallswerte
  • echte Zeitabhängigkeit
  • echte Netzwerke
  • globale Zustände

Zeit, Zufall und Infrastruktur müssen abstrahiert werden.


4. Was sollte getestet werden?

4.1 Domänenlogik (höchste Priorität)

  • Berechnungen
  • Validierungsregeln
  • Entscheidungslogik
  • Zustandsübergänge

4.2 Randfälle (Edge Cases)

  • null / undefined
  • Grenzwerte
  • negative Werte
  • leere Collections
  • Sonderfälle laut Fachkonzept

4.3 Fehlerpfade

  • Exceptions
  • Guard Clauses
  • Fehlerrückgaben
  • Invalid States

Happy Path alleine reicht nicht aus.


5. Was sollte nicht getestet werden?

  • Framework-interne Mechanik
  • Getter/Setter ohne Logik
  • reine DTOs ohne Verhalten
  • fremde Bibliotheken
  • Template-Bindings ohne Logik

Nicht jede Codezeile verdient einen Test.


6. Mocks und Test Doubles

6.1 Wann mocken?

Mocks sind zulässig für:

  • externe Abhängigkeiten
  • Infrastruktur
  • HTTP
  • Datenbanken
  • Messaging

6.2 Wann nicht mocken?

Nicht mocken:

  • reine Domänenlogik
  • Value Objects
  • deterministische Berechnungen

6.3 Over-Mocking vermeiden

Zu viele Mocks führen zu:

  • fragilen Tests
  • starker Kopplung an Implementierungsdetails
  • Scheinsicherheit

Wenn ein Test bei jeder internen Umstrukturierung bricht, ist er falsch geschnitten.


7. Coverage-Arten

Line Coverage

Wurde Code ausgeführt?

Branch Coverage

Wurden alle Verzweigungen getestet?

Condition Coverage

Wurden boolesche Bedingungen vollständig evaluiert?

Mutation Testing (fortgeschritten)

Erkennen Tests echte Logikfehler?

Empfehlung:

  • Branch Coverage > Line Coverage
  • Kritische Kernlogik priorisieren
  • Coverage als Indikator, nicht als Ziel

8. Testbarkeit als Architekturindikator

Testbarkeit ist ein Qualitätsmerkmal der Architektur.

Gut testbarer Code zeichnet sich aus durch:

  • klare Verantwortlichkeiten (SRP)
  • geringe Kopplung
  • Dependency Injection
  • entkoppelte Side Effects
  • framework-unabhängigen Domain Layer

Wenn etwas schwer testbar ist, ist es meist schlecht geschnitten.


9. Naming und Lesbarkeit

describe definiert Kontext.
it beschreibt Verhalten.

Beispiele:

  • returns false if amount is negative
  • throws when id is undefined

Vermeiden:

  • „should call method“
  • Implementierungsdetails im Namen

10. Typische Anti-Patterns

  • Tests auf private Methoden
  • sehr große Testfälle
  • viele lose Assertions ohne Zusammenhang
  • Snapshot-Tests für Logik
  • flakey Tests
  • Bedingungslogik im Test
  • Tests, die nur für Coverage existieren

11. Unit Tests im Kontext von AI

AI kann Code generieren, aber kein Domänenverständnis garantieren.

Risiken ohne Tests:

  • verdeckte Architekturverstöße
  • fragile Implementierungen
  • falsch verstandene Business-Regeln

Unit Tests sichern:

  • Domänenwissen
  • Konsistenz
  • langfristige Wartbarkeit

AI erhöht Produktivität – Tests sichern Qualität.


12. Definition of Done

Ein Feature gilt nicht als fertig, wenn:

  • keine Unit Tests existieren
  • nur der Happy Path getestet wurde
  • Fehlerfälle fehlen
  • Randfälle ignoriert wurden
  • Tests nur formell, aber nicht fachlich sinnvoll sind

Kernaussage

Unit Tests sind kein optionales Tool.
Sie sind ein Ausdruck professioneller Engineering-Kultur.

In komplexen Systemen mit wachsender Codebasis, heterogenen Teams und AI-unterstützter Entwicklung sichern sie nicht nur Codequalität –
sie sichern langfristige Veränderbarkeit.