Home

03 Quiz: Testing Grundlagen - Unit Testing und Clean Code

quiz chapter-03 testing pytest unit-testing test-pyramid clean-code

Anleitung

Dieses Quiz testet dein Verständnis der Kernkonzepte aus Kapitel 03 (Testing-Grundlagen): Testing Grundlagen.

Warum dieses Quiz machen?

Für dein Lernen:

Für Kursverbesserung:

Richtlinien:


Frage 1: Code Quality vs. Code Correctness

Deine CI-Pipeline zeigt alle Checks als bestanden an (Ruff, Pyright). Was sagt dir das über deinen Code?

A) Der Code ist produktionsbereit und fehlerfrei
B) Der Code folgt Style-Guidelines und hat korrekte Typen, kann aber trotzdem Logikfehler enthalten
C) Der Code wird alle Edge Cases korrekt behandeln
D) Der Code wurde gründlich getestet

Antwort anzeigen

Richtige Antwort: B

Code Quality Tools (Ruff, Pyright) prüfen Style und Typen, nicht Korrektheit. Sie verifizieren:

  • ✅ Formatierung (PEP8-Konformität)
  • ✅ Type Hints sind vorhanden und konsistent
  • ✅ Ungenutzte Imports wurden entfernt

Sie verifizieren NICHT:

  • ❌ Logik ist korrekt
  • ❌ Edge Cases werden behandelt
  • ❌ Funktionen geben korrekte Ergebnisse zurück

Deshalb brauchen wir Tests - um zu verifizieren, dass Code tatsächlich wie beabsichtigt funktioniert, nicht nur gut aussieht.


Frage 2: Die Testing Pyramid

Was ist die empfohlene Verteilung von Tests in der Testing Pyramid?

A) Gleichmäßige Verteilung: 33% Unit, 33% Modul, 33% End-to-End
B) Top-lastig: 10% Unit, 20% Modul, 70% End-to-End
C) Unten-lastig: 70% Unit, 20% Modul, 10% End-to-End
D) Mitten-lastig: 10% Unit, 80% Modul, 10% End-to-End

Antwort anzeigen

Richtige Antwort: C

Die Testing Pyramid empfiehlt:

  • 70% Unit Tests (Basis der Pyramide) - Schnell, günstig, einfach zu debuggen
  • 20% Modul/Integrationstests (Mitte) - Moderate Geschwindigkeit und Kosten
  • 10% End-to-End Tests (Spitze) - Langsam, teuer, schwer zu debuggen

Warum diese Verteilung?

  • Schnelle Feedback-Schleifen: Unit Tests laufen in Millisekunden, ermöglichen schnelle Iteration
  • Einfaches Debuggen: Wenn ein Unit Test fehlschlägt, weißt du genau, welche Funktion defekt ist
  • Wartbarkeit: Unit Tests sind einfacher zu schreiben und zu warten als E2E Tests
  • Kosteneffektiv: 50 Unit Tests laufen schneller als 5 E2E Tests

Die Pyramidenform spiegelt sowohl Geschwindigkeit als auch Menge wider - mehr Tests auf der schnellen, günstigen Ebene.


Frage 3: Das Test Cone Anti-Pattern

Was ist der “Test Cone” (invertierte Pyramide) und warum ist er problematisch?

A) Eine Testing-Strategie, die sich auf Integrationstests fokussiert, was der effizienteste Ansatz ist
B) Ein Muster, bei dem die meisten Tests langsame End-to-End Tests sind, was zu langsamem Feedback und brüchigen Tests führt
C) Ein moderner Testing-Ansatz, der die veraltete Testing Pyramid ersetzt
D) Eine Methode, um Testdateien in Verzeichnissen zu organisieren

Antwort anzeigen

Richtige Antwort: B

Der Test Cone ist ein Anti-Pattern, bei dem die Testing Pyramid invertiert ist:

  • Viele E2E Tests (Spitze der Pyramide wird breit)
  • Wenige oder keine Unit Tests (Basis der Pyramide wird schmal)

Probleme mit dem Test Cone:

  1. Langsames Feedback: Jede Änderung erfordert das Ausführen langsamer E2E Tests (Minuten, nicht Sekunden)
  2. Schwer zu debuggen: Wenn E2E Test fehlschlägt, welches der 10 Module hat es verursacht?
  3. Brüchig: UI-Änderungen brechen viele E2E Tests
  4. Teufelskreis: Tests sind langsam → Entwickler vermeiden es, sie auszuführen → Bugs häufen sich an

Wie es passiert:

  • “Ich weiß nicht, wie man Unit Tests schreibt”
  • “Die UI ist einfach manuell durchzuklicken”
  • “Unit Tests sind zu viel Arbeit”

Lösung: Lerne Unit Tests richtig zu schreiben (diese Vorlesung!) und baue die Pyramide korrekt auf.


Frage 4: Unit Test Eigenschaften

Welches der folgenden ist KEINE Eigenschaft eines guten Unit Tests?

A) Testet eine einzelne Funktion oder Methode isoliert
B) Erfordert das Starten der gesamten Anwendung (Dash, Datenbank, etc.)
C) Läuft in Millisekunden
D) Hat klare Erwartungen (Arrange-Act-Assert Muster)

Antwort anzeigen

Richtige Antwort: B

Gute Unit Tests sollten:

  • Isoliert testen: Eine Funktion/Methode, keine Abhängigkeiten von anderen Systemen
  • Schnell sein: Laufen in Millisekunden (nicht Sekunden oder Minuten)
  • Klar sein: Einfach zu verstehen, was getestet wird
  • Unabhängig sein: Können in jeder Reihenfolge ausgeführt werden

Unit Tests sollten NICHT:

  • ❌ Die gesamte Anwendung starten
  • ❌ Sich mit Datenbanken, APIs oder externen Services verbinden
  • ❌ Manuelles Setup erfordern (wie durch die UI klicken)
  • ❌ Von anderen Tests abhängen, die zuerst ausgeführt werden

Beispiel:

# ✅ Guter Unit Test - testet find_intersection() isoliert
def test_find_intersection_normal_angle():
    x, y, dist = find_intersection(test_data)
    assert dist > 0

# ❌ Kein Unit Test - erfordert Start der Dash App
def test_full_application_flow():
    app = start_dash_app()  # Zu langsam!
    response = app.click_button()
    assert response.status == 200

Frage 5: Das AAA Pattern

Wofür steht das AAA Pattern beim Unit Testing?

A) Always Assert Accurately (Immer genau prüfen)
B) Arrange-Act-Assert (Vorbereiten-Ausführen-Prüfen)
C) Analyze-Apply-Approve (Analysieren-Anwenden-Genehmigen)
D) Automatic-Assertion-Analysis (Automatische-Assertions-Analyse)

Antwort anzeigen

Richtige Antwort: B

Das AAA Pattern ist eine Standardstruktur für Unit Tests:

  1. Arrange (Vorbereiten): Testdaten und Bedingungen einrichten
  2. Act (Ausführen): Die zu testende Funktion aufrufen
  3. Assert (Prüfen): Verifizieren, dass das Ergebnis den Erwartungen entspricht

Beispiel:

def test_find_intersection_normal_angle():
    # ARRANGE: Testdaten einrichten
    x_road = np.array([0, 10, 20, 30])
    y_road = np.array([0, 2, 4, 6])
    angle = -10.0

    # ACT: Funktion aufrufen
    x, y, dist = find_intersection(x_road, y_road, angle)

    # ASSERT: Ergebnis verifizieren
    assert x is not None
    assert dist > 0

Warum dieses Pattern?

  • Lesbarkeit: Klare Struktur macht Tests einfach zu verstehen
  • Debugging: Einfach zu sehen, was getestet wird und was fehlgeschlagen ist
  • Konsistenz: Alle Tests folgen dem gleichen Muster

Frage 6: Exhaustives Testing

Warum ist exhaustives Testing (Testen aller möglichen Eingaben) für die meisten Funktionen unmöglich?

A) Weil Computer zu langsam sind, um so viele Tests auszuführen
B) Weil die Anzahl möglicher Eingabekombinationen exponentiell wächst und es unpraktisch macht
C) Weil wir nicht genug Festplattenspeicher haben, um alle Tests zu speichern
D) Weil Testing-Frameworks Limits für die Anzahl der Tests haben

Antwort anzeigen

Richtige Antwort: B

Exhaustives Testing ist unmöglich wegen:

  • Kombinatorische Explosion: Jeder Parameter multipliziert die Anzahl der Testfälle
  • Unendliche oder nahezu unendliche Eingaberäume: Floats, Strings, Arrays haben zu viele mögliche Werte
  • Zeitbeschränkungen: Testen aller Kombinationen würde länger dauern als das Alter des Universums

Beispiel: Eine Funktion mit nur 3 Float-Parametern:

  • Jeder Parameter: ~1.000.000 mögliche Werte (konservative Schätzung)
  • Gesamtkombinationen: 1.000.000³ = 1 Trillion Tests
  • Zeit bei 1ms pro Test: ~32 Milliarden Jahre

Was wir stattdessen tun:

  • Intelligente Testing-Strategien: Äquivalenzklassen, Grenzwertanalyse (Kapitel 03 (Grenzwertanalyse))
  • Risikobasiertes Testing: Fokus auf kritische Funktionalität
  • Repräsentative Stichproben: Wähle Eingaben, die verschiedene Szenarien repräsentieren

Kernaussage: Du kannst nie alles testen, daher musst du strategisch vorgehen, was du testest.


Frage 7: pytest Vorteile

Was ist der HAUPTVORTEIL von pytest gegenüber manuellem Testing (z.B. print Statements)?

A) pytest lässt Tests schneller laufen
B) pytest entdeckt, organisiert und berichtet Testergebnisse automatisch mit detaillierten Fehlerinformationen
C) pytest reduziert die Anzahl der Tests, die du schreiben musst
D) pytest eliminiert die Notwendigkeit, Assertions zu schreiben

Antwort anzeigen

Richtige Antwort: B

pytest bietet Automatisierung und Organisation für Testing:

Mit pytest:

  • Auto-Discovery: Findet alle test_*.py Dateien automatisch
  • Umfangreiche Ausgabe: Zeigt genau, welcher Test fehlgeschlagen ist, Zeilennummer, Fehlertyp
  • Organisierte Berichte: Klare Pass/Fail Zählung, Ausführungszeit
  • Test-Isolation: Jeder Test läuft unabhängig
  • CI/CD Integration: Funktioniert nahtlos mit GitHub Actions
  • Einfache Syntax: Nutze einfach assert (keine speziellen Methoden nötig)

Ohne pytest (manuelles Testing):

  • ❌ Muss jede Testfunktion manuell aufrufen
  • ❌ Bekommt nur print Statements (keine strukturierte Ausgabe)
  • ❌ Keine klare Zusammenfassung, was bestanden/fehlgeschlagen ist
  • ❌ Muss eigene Fehlerbehandlung schreiben
  • ❌ Tests könnten sich gegenseitig beeinflussen

Beispielvergleich:

# Manuell - primitiv und mühsam
def manual_test():
    if result != expected:
        print("FAILED")
        return False
    return True

# pytest - sauber und leistungsstark
def test_something():
    assert result == expected  # pytest erledigt den Rest!

Frage 8: Ein Konzept pro Test

Warum ist es wichtig, nur EIN Konzept pro Testfunktion zu testen?

A) Um Tests schneller laufen zu lassen, indem die Menge an Code reduziert wird
B) Um Speicher zu sparen durch weniger Variablen
C) Um sofort klar zu machen, was kaputt ist, wenn ein Test fehlschlägt
D) Um Python Namenskonventionen zu folgen

Antwort anzeigen

Richtige Antwort: C

Ein Konzept pro Test bedeutet, dass jeder Test ein einzelnes Verhalten oder Szenario verifiziert.

Vorteile:

  • Klare Fehler: Testname sagt dir genau, was kaputt ist
  • Einfaches Debugging: Keine Notwendigkeit, den ganzen Test zu lesen, um das Problem zu finden
  • Bessere Organisation: Verwandte Tests sind gruppiert aber getrennt
  • Einfachere Wartung: Kann ein Konzept modifizieren/entfernen ohne andere zu beeinflussen

Beispiel einer Verletzung:

# ❌ Schlecht: Mehrere Konzepte in einem Test
def test_find_intersection_everything():
    # Konzept 1: Downward Angle
    assert find_intersection(..., -10.0)[0] is not None

    # Konzept 2: Vertical Angle
    assert find_intersection(..., 90.0)[0] is None

    # Konzept 3: Leere Arrays
    assert find_intersection([], [], -10.0)[0] is None

Wenn dieser Test fehlschlägt, welches Konzept ist kaputt? Du musst untersuchen.

Besserer Ansatz:

# ✅ Gut: Ein Konzept pro Test
def test_find_intersection_finds_intersection_for_downward_angle():
    assert find_intersection(..., -10.0)[0] is not None

def test_find_intersection_returns_none_for_vertical_angle():
    assert find_intersection(..., 90.0)[0] is None

def test_find_intersection_returns_none_for_empty_arrays():
    assert find_intersection([], [], -10.0)[0] is None

Wenn jetzt ein Test fehlschlägt: Testname sagt dir sofort, welches Szenario kaputt ist!


Frage 9: Beschreibende Testnamen

Welcher Testname folgt dem empfohlenen Muster: test_[funktion]_[szenario]_[erwartetes_verhalten]()?

A) test_1()
B) test_intersection()
C) test_find_intersection_returns_none_for_empty_arrays()
D) test_edge_case()

Antwort anzeigen

Richtige Antwort: C

Gute Testnamen sind beschreibend und folgen einem Muster:

  • test_[funktion]_[szenario]_[erwartetes_verhalten]()

Analyse jeder Option:

A) test_1() - ❌ Sagt dir nichts

  • Welche Funktion? Welches Szenario? Was wird erwartet?
  • Nutzlos wenn Test fehlschlägt: “test_1 failed” bedeutet nichts

B) test_intersection() - ❌ Zu vage

  • Welche Funktion? Welches Szenario?
  • Nicht spezifisch genug, um zu verstehen, was fehlgeschlagen ist

C) test_find_intersection_returns_none_for_empty_arrays() - ✅ Perfekt!

  • Funktion: find_intersection
  • Szenario: leere Arrays
  • Erwartetes Verhalten: gibt None zurück
  • Wenn es fehlschlägt: “Empty Array Handling ist kaputt” - sofort klar!

D) test_edge_case() - ❌ Nicht spezifisch

  • Welcher Edge Case?
  • Welche Funktion?

Reale Auswirkung:

# Unhilfreiche Fehlermeldung:
❌ FAILED test_1

# Hilfreiche Fehlermeldung:
✅ FAILED test_find_intersection_returns_none_for_empty_arrays
   # Du weißt sofort: "Oh, Empty Array Handling ist kaputt!"

Frage 10: Feedback Loop Geschwindigkeit

Warum ist die Geschwindigkeit von Tests wichtig für die Entwicklungs-Feedback-Schleife?

A) Schnellere Tests verbrauchen weniger Strom
B) Schnelle Tests können während der Entwicklung häufig ausgeführt werden und fangen Bugs sofort
C) Schnelle Tests benötigen weniger leistungsstarke Computer
D) Geschwindigkeit ist nur in CI/CD wichtig, nicht bei lokaler Entwicklung

Antwort anzeigen

Richtige Antwort: B

Schnelle Tests ermöglichen schnelle Iteration:

Mit schnellen Unit Tests (Millisekunden):

  • ✅ Tests nach jeder Codeänderung ausführen
  • ✅ Bugs sofort fangen (innerhalb von Sekunden)
  • ✅ Im Flow-Zustand bleiben (kein Warten)
  • ✅ Bugs fixen während Kontext frisch ist
  • ✅ Entwickler führen Tests tatsächlich aus (werden nicht vermieden)

Mit langsamen E2E Tests (Minuten):

  • ❌ Tests selten ausführen (zu langsam)
  • ❌ Bugs häufen sich zwischen Testläufen an
  • ❌ Kontextwechsel während des Wartens (Kaffee holen, E-Mails checken)
  • ❌ Bugs Stunden später fixen (vergessen was geändert wurde)
  • ❌ Entwickler vermeiden Tests (zu langsam)

Realer Vergleich:

Ändere eine Zeile in find_intersection()

Schnelle Feedback-Schleife:
  Code schreiben → 50 Unit Tests ausführen (0.5s) → Sofortiges Ergebnis
  → Bei Bedarf fixen → Schnell iterieren

Langsame Feedback-Schleife:
  Code schreiben → 50 E2E Tests ausführen (250s) → 4+ Minuten warten
  → Abgelenkt werden → Kontext verlieren → Schwerer zu debuggen

Kernprinzip: Je schneller das Feedback, desto produktiver der Entwickler.


Frage 11: Mehrere Asserts in einem Test

Wann ist es akzeptabel, mehrere assert Statements in einem einzelnen Test zu haben?

A) Nie - immer in separate Tests aufteilen
B) Wenn sie verschiedene Aspekte desselben Konzepts testen
C) Wenn du Zeit sparen willst, indem du Tests kombinierst
D) Nur wenn mehr als drei Dinge getestet werden

Antwort anzeigen

Richtige Antwort: B

Mehrere Asserts sind akzeptabel, wenn sie dasselbe Konzept testen:

✅ Gut: Mehrere Asserts für ein Konzept (Koordinatengültigkeit)

def test_find_intersection_returns_valid_coordinates():
    """Test dass Koordinaten innerhalb erwarteter Grenzen sind."""
    x, y, dist = find_intersection(...)

    # Alle verifizieren dasselbe Konzept: gültige Koordinatengrenzen
    assert x is not None
    assert y is not None
    assert 0 <= x <= 30
    assert 0 <= y <= 6

Warum das OK ist: Alle Asserts prüfen “Koordinatengültigkeit” - ein Konzept mit mehreren Aspekten.

❌ Schlecht: Mehrere Asserts für verschiedene Konzepte

def test_find_intersection_coordinates_and_distance():
    x, y, dist = find_intersection(...)
    assert x > 0     # Konzept 1: x Position
    assert dist > 0  # Konzept 2: Distanz (unterschiedlich!)

Warum das schlecht ist: Testet zwei unabhängige Konzepte (Position und Distanz).

Richtlinie:

  • Gleiches Konzept (Koordinatengültigkeit, Fehlerbehandlung) → Mehrere Asserts OK
  • Verschiedene Konzepte (Position vs. Distanz, Input vs. Output) → Tests aufteilen

Teste dich selbst: Wenn du nicht beschreiben kannst, was der Test prüft in einem Satz, teile ihn auf.


Frage 12: Test-Isolation

Was bedeutet “Test-Isolation” und warum ist sie wichtig?

A) Tests sollten in separaten Ordnern ausgeführt werden
B) Jeder Test sollte unabhängig laufen ohne von anderen Tests oder externen Systemen abhängig zu sein
C) Tests sollten von der Hauptcodebasis isoliert sein
D) Tests sollten nur auf isolierten CI-Servern laufen

Antwort anzeigen

Richtige Antwort: B

Test-Isolation bedeutet, dass jeder Test:

  • Unabhängig läuft (hängt nicht von anderen Tests ab)
  • ✅ In jeder Reihenfolge laufen kann
  • ✅ Keine Seiteneffekte hat (modifiziert keinen globalen Zustand)
  • ✅ Nicht von externen Systemen abhängt (Datenbanken, APIs, UI)

Warum Isolation wichtig ist:

1. Parallele Ausführung:

# Isolierte Tests können parallel laufen
$ pytest -n auto  # Führt Tests gleichzeitig aus

2. Zuverlässiges Debugging:

# ✅ Isoliert: Kann nur diesen einen Test ausführen
$ pytest tests/test_geometry.py::test_find_intersection_normal_angle

# ❌ Nicht isoliert: Muss erst test_setup() ausführen
# Kann nicht isoliert debuggen

3. Keine instabilen Tests:

# ❌ Nicht isoliert - hängt von Testreihenfolge ab
def test_a():
    global_state = 5  # Setzt globalen Zustand

def test_b():
    assert global_state == 5  # Schlägt fehl wenn test_a nicht zuerst läuft!

# ✅ Isoliert - jeder Test setzt seine eigenen Daten
def test_a():
    local_state = 5
    assert local_state == 5

def test_b():
    local_state = 10
    assert local_state == 10

Wie pytest hilft:

  • Jede Testfunktion läuft in ihrem eigenen Scope
  • Kein geteilter Zustand zwischen Tests
  • Tests können in jeder Reihenfolge laufen (oder parallel)

Bewertungsrichtlinie


Wichtige Erkenntnisse zum Merken

  1. Code Quality ≠ Code Correctness - CI prüft Style, Tests prüfen Logik
  2. Testing Pyramid: 70% Unit, 20% Modul, 10% E2E - Schnelle Tests an der Basis
  3. Test Cone ist ein Anti-Pattern - Invertierte Pyramide führt zu langsamen, brüchigen Tests
  4. AAA Pattern: Arrange-Act-Assert - Standardstruktur für lesbare Tests
  5. Exhaustives Testing ist unmöglich - Muss intelligente Testing-Strategien verwenden
  6. pytest bietet Automatisierung - Auto-Discovery, umfangreiche Ausgabe, CI/CD Integration
  7. Ein Konzept pro Test - Klare Fehler, einfaches Debugging
  8. Beschreibende Testnamen - test_[funktion]_[szenario]_[erwartet]()
  9. Schnelle Feedback-Schleifen sind wichtig - Unit Tests ermöglichen schnelle Iteration
  10. Test-Isolation ist kritisch - Unabhängige Tests können in jeder Reihenfolge laufen
  11. Mehrere Asserts OK für gleiches Konzept - Aber verschiedene Konzepte aufteilen
  12. Unit Tests testen isoliert - Keine Abhängigkeiten von externen Systemen

Was kommt als Nächstes?

Kapitel 03 (Grenzwertanalyse) wird behandeln:

Übe vorerst:

Probiere das: Nimm eine Funktion, die du geschrieben hast, und schreibe 3 Unit Tests dafür mit dem, was du heute gelernt hast!

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk