Strategie: Jede Dimension abdecken, nicht alle Kombinationen
# Gewichtsdimension (fixiere distance=50, express=False)
test_light_weight() # 2.5 kg → €5
test_medium_weight() # 10 kg → €10
test_heavy_weight() # 30 kg → €20
# Entfernungsdimension (fixiere weight=2.5, express=False)
test_local_distance() # 50 km → €5 × 1.0
test_regional_distance() # 250 km → €5 × 1.5
test_long_distance() # 1000 km → €5 × 2.0
# Express-Dimension (fixiere weight=2.5, distance=50)
test_standard_shipping() # €5 × 1.0
test_express_shipping() # €5 × 1.5
# Grenzwerttests + Fehlertests
Ergebnis: ~10 strategische Tests statt 18!
Wann diskret EINFACHER ist:
Ausgabekategorien sind offensichtlich (einfach Rückgabewerte auflisten!)
Keine Granularitätsdebatten (Funktionssignatur definiert Klassen)
Vollständige Abdeckung erreichbar (6 Noten? Schreibe 6 Tests!)
Wann diskret SCHWIERIGER ist:
Viele mögliche Ausgaben (8 Validierungsfehler = 8 Tests minimum)
Kombinatorische Explosion (3 × 3 × 2 = 18 Klassen)
Hierarchische Validierung (Reihenfolge ist wichtig - Early Returns)
Grenzwerte bleiben kritisch (89 vs 90 kann Bugs verbergen)
Fazit: Diskret vereinfacht Ausgabepartitionierung, nicht Eingabeanalyse!
Verwende diskrete Analyse wenn:
Verwende kontinuierliche Analyse wenn:
Verwende hybride Analyse wenn:
Sieht kontinuierlich aus:
tuple[float, float] | None (Koordinaten)Aber Ergebnisse sind diskret:
NoneWichtige Erkenntnis: Obwohl Eingaben kontinuierlich sind (Floats), sind die Ergebnisse diskret!
Teststrategie:
Diskrete Ergebniskategorien identifizieren, dann repräsentative Eingaben für jede wählen!
Genau das haben wir mit calculate_shipping_cost gemacht!
Muster:
Aus deinem Road Profile Viewer Projekt:
def find_intersection(
x_road: np.ndarray, # Road profile arrays
y_road: np.ndarray,
angle_degrees: float, # Ray direction
camera_x: float = 0, # Camera position
camera_y: float = 1.5
) -> tuple[float, float] | None:
# ... 70 lines of geometric logic ...
Denk darüber nach: Wie würdest du das testen?
Wichtig: Dieses Beispiel ist so komplex, dass wir eine separate detaillierte Anleitung mit 22 visuellen Illustrationen erstellt haben!
Siehe das "Vertiefung - Testen von find_intersection()" Foliendeck für die vollständige Analyse.
Vollständige Details:
Interaktive Umfrage:
Wie viele Tests brauchen wir für
find_intersection()?A) 5-10 Tests
B) 20-30 Tests
C) 50-100 Tests
D) Mehr als 100 Tests
Diskutiere mit deinem Nachbarn! (30 Sekunden)
x_road und y_road Arrays:
len = 0) → Fehlerbehandlunglen = 1) → degenerierter Falllen = 2) → ein Segment, minimal gültiglen = 3) → zwei Segmentelen > 3) → mehrere Segmente, typischer Fallangle_degrees:
−90° < angle < 0°)angle = 0°)0° < angle < 90°)angle = ±90°) → keine Steigung, spezielle Behandlung|angle| > 90°) → ungültige EingabeWarum das wichtig ist:
Basierend darauf, was die Funktion zurückgibt:
(x, y)NoneNoneWarum das wichtig ist:
Geometrische Eigenschaften:
Warum das wichtig ist:
Für find_intersection() haben wir identifiziert:
Rechnen wir mal...
(Wir haben die Straßenform vereinfacht, um über 1000 zu vermeiden!)
Realitätscheck: Kannst du 600 Tests schreiben und pflegen?
Spoiler: Nein! Hier kommt menschliche Einsicht ins Spiel...
Du denkst vielleicht: "Frag einfach ChatGPT/Claude, alle 600 Tests zu generieren!"
Probleme mit KI-generierten Tests:
Fazit: LLMs können helfen, aber nicht ersetzen menschliche Einsicht!
Menschliche Einsicht:
Ergebnis: Menschen können 600 → 20-30 strategische Tests reduzieren basierend auf Domänenwissen.
Das ist dein Wert als Softwareentwickler:in!
Strategie: Decke jede Dimension mit 2-3 Repräsentanten ab
Rechnung: 3 + 3 + 2 + 4 + 2 = 14 Basis-Tests
Plus Sonderfälle: Vertikaler Strahl, paralleles Segment, außerhalb Bereich → ~10 mehr
Gesamt: ~25-30 Tests
Willst du die tatsächlichen Tests sehen? Sieh dir das "Vertiefung - Testen von find_intersection()" Foliendeck für alle 22 Tests mit visuellen Diagrammen an!
class TestFindIntersection:
# Strukturelle Dimension
def test_empty_arrays(self): ...
def test_two_point_road(self): ...
def test_many_point_road(self): ...
# Winkel-Dimension
def test_downward_ray(self): ...
def test_horizontal_ray(self): ...
def test_upward_ray(self): ...
def test_vertical_ray(self): ... # Sonderfall
# Ergebnis-Dimension
def test_intersection_found(self): ...
def test_no_intersection(self): ...
def test_ray_parallel_to_segment(self): ...
class TestFindIntersection:
# ... (vorherige Tests)
# Intelligente Kombinationen (Domain-getrieben)
def test_camera_on_road_horizontal_ray(self): ...
def test_camera_above_road_downward_ray_hits(self): ...
def test_camera_below_road_upward_ray_hits(self): ...
def test_camera_far_right_upward_ray_misses(self): ...
def test_flat_road_any_angle(self): ...
def test_peaked_road_multiple_intersections(self): ...
# Sonderfälle
def test_ray_through_road_endpoint(self): ...
def test_angle_out_of_range(self): ...
Mensch: Klassen identifizieren, Granularität wählen, Risiken priorisieren, überprüfen
LLM: Boilerplate generieren, Testdaten erstellen, Assertions schreiben, Sonderfälle vorschlagen
Workflow: Du entwirfst → LLM implementiert → Du überprüfst!
Gute Prompts:
find_intersection mit flacher Zwei-Punkt-Straße und horizontalem Strahl"None erwartet wenn der Strahl die Straße verfehlt" Schlechte Prompts:
find_intersection" → produziert minderwertiges RauschenSchlüssel: Sei spezifisch über Äquivalenzklassen und erwartetes Verhalten!
Szenario: Du testest find_intersection() für ein selbstfahrendes Auto.
Diskussionsfragen:
Think-Pair-Share: Diskutiere mit deinem Nachbarn! (2 Minuten)
1. Äquivalenzklassen reduzieren unendliche Eingaben auf endliche Tests
2. Ausgabe-Kategorien bestimmen Eingabe-Klassen
reciprocal_sum(x,y,z): 3 Ausgabeverhalten → 3 Klassen3. Granularität ist eine Design-Entscheidung
4. White-Box-Testing offenbart verborgene Äquivalenzklassen
reciprocal_sum Code zeigt Summe ist entscheidend5. Reale Funktionen haben kombinatorische Komplexität
find_intersection: 5 Dimensionen × viele Klassen = 600 Tests6. Menschliche Einsicht ist unersetzlich
Testing-Pyramide (Erinnerung):
/ \
/ E2E \ 10% - End-to-End (gesamtes System)
/_______\
/ \
/Integration\ 20% - Integration (Module zusammen)
/_____________\
/ \
/ Unit Tests \ 70% - Unit Tests (einzelne Funktionen)
/___________________\
Heutiger Fokus: Wie wählt man welche Unit-Tests effizient schreibt!
Nächstes Thema: Grenzwertanalyse (Bugs an Rändern finden)
Äquivalenzklassen-Testen ist mächtig, aber nicht genug!
Problem: Bugs verstecken sich oft an Grenzen zwischen Klassen.
Beispiel für reciprocal(x):
x = 5.0, x = -5.0, x = 0.0 x = 0.0000001? (sehr nah an Null) x = 1e308? (sehr groß, nah am Float-Maximum)Grenzwertanalyse sagt: Teste an den Rändern der Äquivalenzklassen!
Nächste Vorlesung: Wie man Grenzen systematisch findet und testet.
Aufgabe: Wähle Äquivalenzklassen für diese Funktion:
def classify_temperature(temp_celsius: float) -> str:
"""Classify temperature for weather app"""
if temp_celsius < 0:
return "freezing"
elif temp_celsius < 10:
return "cold"
elif temp_celsius < 20:
return "mild"
elif temp_celsius < 30:
return "warm"
else:
return "hot"
Nimm dir 2 Minuten: Arbeite allein oder mit einem Partner
Für classify_temperature(temp_celsius):
Wie viele Äquivalenzklassen?
Welche sind es?
Welche repräsentativen Werte würdest du wählen?
Bonus: Welche Grenzwerte solltest du testen?
Vollständige Vorlesung: Kapitel 03 (Testing-Grundlagen) - Testing-Grundlagen
Für jede Funktion, frage:
Ergebnis: Endliche, handhabbare Test-Suite die unendlichen Eingaberaum abdeckt!
Denk dran: Du testest nicht alle Eingaben — du testest alle Verhaltensweisen!