Skip to content
IRC-CodingIRC-Coding
Unit TestsTestdoublesAAA Arrange Act AssertIsolierte TestsSchnelle TestsAussagekräftige TestsWartbare Tests

Eigenschaften guter Unit-Tests: isoliert, schnell, aussagekräftig, wartbar & einfach durchführbar

Gute Unit‑Tests: korrekt, isoliert, schnell , aussagekräftig , wartbar und einfach durchführbar. Mit Testdoubles, AAA und Prüfungsfragen.

S

schutzgeist

11 min read
Eigenschaften guter Unit-Tests: isoliert, schnell, aussagekräftig, wartbar & einfach durchführbar

Eigenschaften guter Unit‑Tests

Dieser Beitrag ist eine Erklärung zu Eigenschaften guter Unit‑Tests – inklusive Prüfungsfragen, Kernkomponenten und Tags.

Gute Unit‑Tests sind der Sicherheitsgurt jeder Software. Sie helfen Dir, schnell zu erkennen, ob eine Änderung etwas kaputt gemacht hat, und geben Dir das Vertrauen, Code umzubauen, ohne Angst vor Regressionsfehlern zu haben. Gerade in Prüfungen und im Berufsalltag wird erwartet, dass Du nicht nur Tests schreiben, sondern auch bewerten kannst, was einen guten Unit‑Test ausmacht.

In a Nutshell

Gute Unit‑Tests sind korrekt, isoliert, schnell, aussagekräftig, wartbar und einfach durchführbar. Sie geben schnelles, verlässliches Feedback, lokalisieren Fehler präzise und ermöglichen sicheres Refactoring.

Kompakte Fachbeschreibung

Unit‑Tests prüfen die kleinste testbare Einheit eines Programms, meist eine einzelne Methode oder Klasse. Damit sie ihren Zweck erfüllen, müssen sie mehrere Eigenschaften erfüllen:

1. Korrekt

Ein Test muss genau das Verhalten prüfen, das er dokumentieren soll. Falsche Annahmen oder ungenaue Erwartungen machen einen Test wertlos. Ein korrekter Test ist gleichzeitig eine Spezifikation für das Verhalten der Einheit.

2. Isoliert

Ein Unit‑Test sollte keine externen Systeme wie Datenbanken, Netzwerke, Dateisysteme oder APIs benötigen. Solche Abhängigkeiten ersetzt Du durch Testdoubles wie Stubs, Mocks, Fakes oder Spys. Isolierte Tests laufen stabil und schnell.

3. Schnell

Ein guter Unit‑Test läuft im Millisekunden‑Bereich. Das ermöglicht es, Hunderte oder Tausende Tests mehrmals am Tag auszuführen, ohne dass der Entwicklungsfluss unterbrochen wird. Langsame Tests werden schnell ignoriert oder aus der CI-Pipeline entfernt.

4. Aussagekräftig

Der Testname, die Teststruktur und die Fehlermeldung müssen auf den ersten Blick verständlich sein. Eine klare AAA‑Struktur und präzise Assertions helfen, bei einem Fehlschlag sofort zu verstehen, was schief gelaufen ist.

5. Wartbar

Tests sollten genau so sauber strukturiert sein wie Produktionscode. Kein duplizierter Code, keine unnötige Komplexität, gemeinsame Hilfsbibliotheken und klare Abhängigkeiten machen Tests langfristig pflegeleicht. Gute Tests überstehen Refactorings ohne Änderungen.

6. Einfach durchführbar

Tests müssen mit einem einzigen Befehl startbar sein. Sie dürfen keine manuelle Vorbereitung, keine spezielle Umgebung und keine zeitliche Abhängigkeit benötigen. Ihr Ergebnis muss deterministisch sein: gleiche Eingabe, gleiche Ausgabe.

Prüfungsrelevante Stichpunkte

  • AAA (Arrange, Act, Assert): Jeder Unit‑Test besteht aus drei Teilen: Vorbereitung der Daten, Ausführung der zu testenden Methode und Prüfung des Ergebnisses.
  • Testdoubles: Stub liefert vordefinierte Antworten, Mock prüft Interaktionen, Fake ist eine vereinfachte Implementierung, Spy protokolliert Aufrufe.
  • Naming: Ein Testname wie methodName_condition_expectedResult macht das erwartete Verhalten direkt lesbar.
  • Edge Cases und Fehlerfälle: Nicht nur der Happy Path, sondern auch Nullwerte, leere Eingaben, Grenzwerte und Exceptions gehören getestet.
  • Keine „magischen“ Zahlen: Konstanten und Variablen mit sprechenden Namen machen Tests verständlicher und wartbarer.
  • Aussagekräftige Assertions: Assertions sollten eine klare Fehlermeldung liefern, zum Beispiel assertEquals(expected, actual, "Neukunde soll keinen Rabatt erhalten").
  • Unabhängigkeit: Tests dürfen nicht voneinander abhängen. Die Reihenfolge sollte egal sein.
  • Determinismus: Ein Test muss bei jedem Lauf dasselbe Ergebnis liefern. Zufall, Uhrzeit oder externe Zustände haben in Unit‑Tests nichts zu suchen.
  • Ein Test, eine Verantwortung: Jeder Test sollte genau ein Verhalten prüfen. Mehrere Assertions sind erlaubt, sollten aber zusammengehören.
  • CI/CD‑Integration: Unit‑Tests sollten automatisch in der Build‑Pipeline laufen und bei Fehlschlag den Build stoppen.

Kernkomponenten

  1. Teststruktur (AAA) AAA steht für Arrange, Act, Assert. In der Arrange‑Phase bereitest Du alle notwendigen Objekte und Testdaten vor. In der Act‑Phase rufst Du die Methode auf, die Du testen möchtest. In der Assert‑Phase prüfst Du das Ergebnis.

  2. Testdoubles (Stub, Mock, Fake, Spy) Testdoubles ersetzen echte Abhängigkeiten. Ein Stub liefert feste Antworten, ein Mock prüft, ob bestimmte Methoden aufgerufen wurden, ein Fake ist eine vereinfachte Implementierung und ein Spy protokolliert Aufrufe.

  3. Naming‑Konventionen Ein guter Testname beschreibt das Szenario und das erwartete Ergebnis. Muster wie methodName_condition_expectedResult machen Tests zu lesbarer Dokumentation.

  4. Assertions Assertions sind die Prüfungen am Ende eines Tests. Sie sollten präzise sein und bei einem Fehlschlag eine aussagekräftige Meldung liefern.

  5. Testdaten (Factories, Builder) Testdaten sollten einfach erzeugbar und wiederholbar sein. Factories und Builder helfen, komplexe Objekte aufzubauen, ohne den Test mit Boilerplate zu überladen.

  6. Isolation Ein Unit‑Test prüft genau eine Einheit. Datenbanken, Netzwerke, Dateien oder andere Services werden durch Testdoubles ersetzt.

  7. Geschwindigkeit Unit‑Tests müssen im Millisekunden‑Bereich laufen. Nur so bleibt die Test‑Suite bei Tausenden Tests ausführbar und gibt schnelles Feedback.

  8. Unabhängigkeit Tests dürfen nicht aufeinander aufbauen. Jeder Test erzeugt seine eigenen Daten und hinterlässt keine Zustände, die den nächsten Test beeinflussen.

  9. Determinismus Ein Test liefert bei jedem Durchlauf dasselbe Ergebnis. Zufall, Zeit, Netzwerk oder globale Zustände werden durch kontrollierte Eingaben und Testdoubles ersetzt.

  10. Wartbarkeit Tests sind Code. Sie profitieren von DRY, klaren Namen, kleinen Methoden und wenig Kopplung. Wartbare Tests überstehen Refactorings und bleiben verständlich.

Praxisbeispiel (Rabattberechnung)

Das folgende Beispiel zeigt einen typischen Unit‑Test in Java mit JUnit. Es geht um eine Methode berechneRabatt, die einen Rabatt für einen Kunden und einen Artikel berechnet. Der Test prüft den Fall, dass ein Neukunde für einen Standardartikel keinen Rabatt erhält.

Was wird hier gezeigt?

  • Klare Namensgebung: Der Testname beschreibt Methode, Bedingung und erwartetes Ergebnis.
  • AAA‑Struktur: Die Abschnitte Arrange, Act und Assert sind kommentiert und klar getrennt.
  • Fokus: Es wird nur eine einzelne Verhaltensweise geprüft.
  • Aussagekräftige Assertion: Die Fehlermeldung erklärt, warum der Test wichtig ist.
// Naming: berechneRabatt_neukunde_standardartikel_erwartet0Prozent
@Test
public void berechneRabatt_neukunde_standardartikel_erwartet0Prozent() {
    // Arrange
    Kunde kunde = new Kunde(Kundentyp.NEUKUNDE);
    Artikel artikel = new Artikel(Artikeltyp.STANDARD);
    RabattService service = new RabattService();

    // Act
    int rabatt = service.berechneRabatt(kunde, artikel);

    // Assert
    assertEquals(0, rabatt, "Neukunde soll keinen Rabatt erhalten");
}

Erklärung: Der Test ist isoliert, weil er keine externen Systeme nutzt. Er ist schnell, weil nur einfache Objekte erzeugt werden. Er ist aussagekräftig, weil Name und Fehlermeldung sofort verständlich sind. Wenn der Test fehlschlägt, weißt Du sofort, dass die Rabattberechnung für Neukunden falsch ist.

Vorteile und Nachteile

Vorteile

  • Schnelles Feedback: Unit‑Tests laufen in Millisekunden und geben sofort Auskunft, ob eine Änderung funktioniert.
  • Hohe Fehlerlokalisierung: Ein fehlgeschlagener Test zeigt direkt die betroffene Einheit, nicht erst ein großes System.
  • Sicheres Refactoring: Mit einer guten Test‑Suite kannst Du Code umgestalten, ohne Angst vor Regressionsfehlern zu haben.
  • Geringe Wartungskosten bei guter Struktur: Saubere Tests sind leicht zu verstehen und anzupassen, wenn sich Anforderungen ändern.
  • Lebende Dokumentation: Tests beschreiben das Verhalten der Software und helfen neuen Teammitgliedern beim Einstieg.
  • Frühe Fehlererkennung: Fehler werden entdeckt, bevor sie in die Produktion oder in höhere Teststufen gelangen.

Nachteile

  • Initialer Aufwand: Das Schreiben von Testdoubles und guter Testdaten kostet am Anfang Zeit.
  • Gefahr von Over‑Engineering: Zu viele Hilfsbibliotheken, komplexe Factory‑Methoden oder verschachtelte Mocks können die Tests schwerer pflegen als den Produktionscode.
  • Falsche Sicherheit: Ein Test, der nicht das Richtige prüft, kann grüne Ergebnisse vortäuschen, während der Code trotzdem fehlerhaft ist.
  • Wartung der Tests selbst: Bei schlechter Struktur führt jede kleine Änderung zu vielen angepassten Tests, was den Nutzen zunichtemacht.
  • Kein Ersatz für Integrationstests: Unit‑Tests prüfen Einzelteile. Sie ersetzen nicht Tests, die das Zusammenspiel mehrerer Komponenten prüfen.

FAQ: Eigenschaften guter Unit‑Tests

1. Was ist ein Unit‑Test?

Ein Unit‑Test prüft die kleinste testbare Einheit eines Programms, meist eine Methode oder Klasse, isoliert von externen Abhängigkeiten.

2. Was bedeutet AAA in Unit‑Tests?

AAA steht für Arrange, Act, Assert. Zuerst bereitest Du die Testdaten vor, dann führst Du die zu testende Methode aus und schließlich prüfst Du das Ergebnis.

3. Was macht einen Unit‑Test korrekt?

Ein korrekter Unit‑Test prüft genau das Verhalten, das er dokumentieren soll, und basiert auf validen Annahmen. Er ist gleichzeitig eine Spezifikation für die Einheit.

4. Was bedeutet Isolation bei Unit‑Tests?

Isolation bedeutet, dass ein Unit‑Test keine externen Systeme wie Datenbanken, Netzwerke oder Dateisysteme benötigt. Externe Abhängigkeiten werden durch Testdoubles ersetzt.

5. Was ist ein Testdouble?

Ein Testdouble ist ein Ersatz für eine echte Abhängigkeit in einem Test. Dazu gehören Stubs, Mocks, Fakes und Spys.

6. Was ist der Unterschied zwischen Stub und Mock?

Ein Stub liefert vordefinierte Antworten für Abhängigkeiten. Ein Mock prüft zusätzlich, ob bestimmte Methoden mit den erwarteten Parametern aufgerufen wurden.

7. Was ist ein Fake?

Ein Fake ist eine vereinfachte, funktionierende Implementierung einer Abhängigkeit. Ein In‑Memory‑Repository anstelle einer echten Datenbank ist ein typisches Beispiel.

8. Was ist ein Spy?

Ein Spy ist ein Testdouble, das Aufrufe von Methoden protokolliert, ohne dabei vollständig das Verhalten zu simulieren. Er hilft, Interaktionen später zu überprüfen.

9. Warum sollten Unit‑Tests schnell sein?

Schnelle Unit‑Tests ermöglichen häufiges Ausführen während der Entwicklung und in der CI‑Pipeline. Langsame Tests bremsen den Entwicklungsfluss aus und werden oft ignoriert.

10. Was bedeutet aussagekräftig in Bezug auf Tests?

Ein aussagekräftiger Test hat einen klaren Namen, eine verständliche Struktur und präzise Fehlermeldungen. Bei einem Fehlschlag weißt Du sofort, was falsch ist.

11. Was ist eine gute Naming‑Konvention für Unit‑Tests?

Ein beliebtes Muster ist methodName_condition_expectedResult, zum Beispiel berechneRabatt_neukunde_standardartikel_erwartet0Prozent. Alternativ eignet sich sollte_X_wenn_Y.

12. Was ist ein Assertion?

Eine Assertion ist eine Prüfung, die das tatsächliche Ergebnis mit dem erwarteten Ergebnis vergleicht. Beispiele sind assertEquals, assertTrue oder assertThrows.

13. Was sind magische Zahlen in Tests?

Magische Zahlen sind hart kodierte Werte ohne erklärenden Kontext. In Tests solltest Du Konstanten oder Variablen mit sprechenden Namen verwenden, um die Absicht klar zu machen.

14. Warum sollten Tests unabhängig voneinander sein?

Unabhängige Tests hinterlassen keine Zustände für andere Tests und laufen in beliebiger Reihenfolge. Das macht die Suite stabil und verhindert schwer zu findende Seiteneffekte.

15. Was bedeutet deterministisch bei Unit‑Tests?

Ein deterministischer Test liefert bei jedem Lauf mit denselben Eingaben dasselbe Ergebnis. Zufall, Uhrzeit und externe Zustände haben in Unit‑Tests nichts zu suchen.

16. Was versteht man unter Wartbarkeit von Tests?

Wartbare Tests sind sauber strukturiert, vermeiden Duplikate, verwenden sprechende Namen und überstehen Refactorings ohne Änderungen. Sie sind genauso wichtig wie Produktionscode.

17. Was ist der Unterschied zwischen Unit‑, Integrations‑ und E2E‑Tests?

Unit‑Tests prüfen einzelne Einheiten isoliert. Integrations‑Tests prüfen das Zusammenspiel mehrerer Einheiten. End‑to‑End‑Tests prüfen die gesamte Anwendung aus Benutzersicht.

18. Was sind Edge Cases?

Edge Cases sind Randfälle wie leere Eingaben, Nullwerte, maximale Werte oder unerwartete Reihenfolgen. Gute Tests berücksichtigen diese explizit, nicht nur den normalen Fall.

19. Was ist ein Happy Path?

Der Happy Path beschreibt den Standardfall, bei dem alles wie erwartet funktioniert. Tests sollten neben dem Happy Path auch Fehlerfälle und Edge Cases abdecken.

20. Was ist eine Test‑Factory?

Eine Test‑Factory ist eine Hilfsmethode, die Testobjekte mit sinnvollen Standardwerten erzeugt. Sie vermeidet Duplikate und hält Tests übersichtlich.

21. Was ist Refactoring‑Sicherheit bei Tests?

Tests sind refactoring‑sicher, wenn sie auf Verhalten prüfen und nicht auf interne Implementierungsdetails. Dann bleiben sie gültig, wenn der Produktionscode umgestaltet wird.

22. Was ist ein flaky Test?

Ein flaky Test ist ein Test, der manchmal besteht und manchmal fehlschlägt, ohne dass sich der Code geändert hat. Flaky Tests sind meist ein Zeichen für nichtdeterministisches Verhalten oder externe Abhängigkeiten.

23. Was ist ein Code‑Coverage?

Code‑Coverage gibt an, welcher Anteil des Quellcodes von Tests durchlaufen wird. Hohe Coverage allein sagt aber nichts über die Qualität der Tests aus.

24. Warum sollte ein Test nur eine Verantwortung haben?

Wenn ein Test mehrere, unabhängige Verhaltensweisen prüft, ist bei einem Fehlschlag nicht klar, welche Annahme falsch war. Mehrere Assertions sind erlaubt, sollten aber zusammengehören.

25. Was bedeutet einfach durchführbar bei Unit‑Tests?

Ein Unit‑Test muss mit einem Befehl startbar sein, keine manuelle Vorbereitung benötigen und deterministisch sein. Er sollte überall laufen, wo der Code vorhanden ist.

Lernstrategie

1. Verständniseinstieg: Schreibe Deinen ersten Unit‑Test mit AAA

Nimm eine einfache Methode, zum Beispiel eine Methode, die den Rabatt für einen Kunden berechnet. Schreibe einen Test, der die AAA‑Struktur nutzt und den Testnamen nach dem Muster methodName_condition_expectedResult aufbaut.

Aufgabe: Schreibe einen Test für eine Methode istVolljaehrig(int alter), die true zurückgeben soll, wenn das Alter mindestens 18 ist.

@Test
public void istVolljaehrig_alter18_erwartetTrue() {
    // Arrange
    int alter = 18;

    // Act
    boolean ergebnis = pruefer.istVolljaehrig(alter);

    // Assert
    assertTrue(ergebnis, "Eine Person mit Alter 18 sollte volljährig sein");
}

Lösung: Der Test ist korrekt, isoliert und aussagekräftig. Er prüft einen Grenzfall und nutzt eine klare Assertion mit Fehlermeldung.

2. Vertiefung: Testdoubles einsetzen

Stell Dir vor, Du hast eine Klasse BestellService, die auf eine echte Datenbank zugreift. Schreibe einen Test, in dem Du die Datenbank durch einen Stub ersetzt.

Aufgabe: Ein BestellService soll die Gesamtsumme einer Bestellung berechnen. Er nutzt dafür ein PreisRepository. Schreibe einen Test, der das Repository stubbt.

@Test
public void berechneSumme_einArtikel_preis10_erwartet10() {
    // Arrange
    PreisRepository stubRepository = new PreisRepository() {
        @Override
        public double findePreis(String artikelId) {
            return 10.0;
        }
    };
    BestellService service = new BestellService(stubRepository);
    List<String> artikel = List.of("A1");

    // Act
    double summe = service.berechneSumme(artikel);

    // Assert
    assertEquals(10.0, summe, 0.001);
}

Lösung: Der Test isoliert den Service von der Datenbank. Der Stub liefert immer den Preis 10.0, sodass der Test schnell und deterministisch bleibt.

3. Prüfungsfokus: Testdouble‑Arten zuordnen

Übe das Zuordnen von Testdoubles zu Szenarien. Hier ist eine kleine Übung:

SzenarioPassendes Testdouble
Du brauchst eine vordefinierte Antwort für eine Abhängigkeit.Stub
Du möchtest prüfen, ob eine bestimmte Methode aufgerufen wurde.Mock
Du ersetzt eine Datenbank durch eine In‑Memory‑Implementierung.Fake
Du möchtest später überprüfen, welche Aufrufe stattfanden.Spy

4. Refactoring: Schlechte Tests verbessern

Nimm den folgenden schlechten Test und forme ihn in einen guten, wartbaren Test um.

Schlechte Version:

@Test
public void test1() {
    int x = 5;
    int y = 10;
    assertEquals(new Calc().add(x, y), 15);
}

Verbesserte Version:

@Test
public void add_zweiPositiveZahlen_erwartetSumme() {
    // Arrange
    int summand1 = 5;
    int summand2 = 10;
    Calculator calculator = new Calculator();

    // Act
    int ergebnis = calculator.add(summand1, summand2);

    // Assert
    assertEquals(15, ergebnis, "5 + 10 sollte 15 ergeben");
}

Lösung: Der Testname ist aussagekräftig, die Variablennamen sprechen, die AAA‑Struktur ist klar und die Assertion hat eine Fehlermeldung.

5. Integration: Tests in die Build‑Pipeline einbinden

Schreibe oder erweitere ein Projekt so, dass die Unit‑Tests automatisch beim Build laufen. In einem Maven‑Projekt geschieht das mit mvn test, in einem Gradle‑Projekt mit gradle test, in einem Node.js‑Projekt mit npm test.

Aufgabe: Konfiguriere eine CI‑Pipeline, die bei jedem Commit die Tests ausführt und bei einem Fehlschlag den Build stoppt.

Lösung: Ein einfacher GitHub‑Actions‑Workflow kann so aussehen:

name: Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - run: mvn test

6. Selbstkontrolle

Stelle Dir am Ende folgende Fragen:

  • Ist jeder Test isoliert und deterministisch?
  • Sind die Testnamen so beschreibend, dass Du sie als Dokumentation lesen kannst?
  • Prüfen Deine Tests Verhalten statt Implementierungsdetails?
  • Verwendest Du Testdoubles für externe Abhängigkeiten?
  • Laufen alle Tests mit einem einzigen Befehl?

Wenn Du alle Punkte mit Ja beantworten kannst, hast Du die wichtigsten Eigenschaften guter Unit‑Tests verinnerlicht.

Themenanalyse

  • Technischer Kern: AAA‑Struktur, Testdoubles, Assertions, Naming, Isolation
  • Herausforderungen: Aufwand für Testdoubles, Vermeidung von Over‑Engineering, flache Tests
  • Qualitätssicherung: Frühe Fehlererkennung, Refactoring‑Sicherheit, lebende Dokumentation
  • Wirtschaftlichkeit: Schnelles Feedback, geringere Ausfallzeit, bessere Wartbarkeit
  • Prüfungsrelevanz: IHK und Berufspraxis fragen gezielt nach Eigenschaften guter Unit‑Tests und Testdoubles

Wichtigste Quellen

  1. https://martinfowler.com/articles/practical-test-pyramid.html
  2. https://junit.org/junit5/docs/current/user-guide/
  3. https://testing.googleblog.com
Zurück zum DEV Blog
Share:

Ähnliche Beiträge