03 Übung: CFG Tracing
CFG Tracing Übung: Visualisierung von Statement- und Branch-Coverage
Einführung
Diese Übung hilft dir, Code-Coverage zu verstehen, indem du Testfälle durch Kontrollflussgraphen (CFGs) verfolgst. Durch die Visualisierung, welche Knoten (Anweisungen) und Kanten (Branches) abgedeckt werden, entwickelst du ein Gespür dafür, wie Coverage funktioniert.
Vorbereitung: Zuerst lesen
Bevor du diese Übung versuchst, studiere die folgenden Vorlesungsabschnitte:
Aus Kapitel 03 (Testtheorie und Coverage): Testtheorie & Coverage:
- Abschnitt 4: Code Coverage (C0 und C1) - CFG-Beispiele, Knoten/Kanten zählen
- Abschnitt 5: Systematischer Testentwurf für Branch Coverage - Tests aus CFGs ableiten
Lernziele
Nach Abschluss dieser Übungen solltest du in der Lage sein:
- Kontrollflussgraphen zu lesen und zu interpretieren
- Testfallausführung durch Code-Pfade zu verfolgen
- Statement Coverage (C0) aus Knotenabdeckung zu berechnen
- Branch Coverage (C1) aus Kantenabdeckung zu berechnen
- Tests zu entwerfen, um spezifische Coverage-Ziele zu erreichen
- Zu verstehen, warum C1 C0 subsumiert
Anleitung
- Studiere den Code und die CFG-Beschreibungen in jeder Frage
- Verfolge die gegebenen Testfälle durch den Graphen
- Identifiziere, welche Knoten und Kanten abgedeckt werden
- Berechne die Coverage-Prozentsätze
- Klicke, um nach dem Beantworten die Erklärungen anzuzeigen
Zeit: 35-45 Minuten für alle Übungen
Übung 1: Einfacher CFG - classify_number()
Betrachte diesen Code:
def classify_number(x):
if x > 0: # Node 1 (decision)
return "pos" # Node 2
elif x < 0: # Node 3 (decision)
return "neg" # Node 4
else:
return "zero" # Node 5
Testfall:
def test_positive():
assert classify_number(5) == "pos"
Mit Eingabe x = 5:
- Pfad: START → N1 (True) → N2 → END
- Abgedeckte Knoten: N1, N2 (2 von 5 Knoten)
Was ist die Statement Coverage (C0)? Gib eine Zahl ein (ohne %-Zeichen).
Übung 1: Einfacher CFG - classify_number() (Fortsetzung)
Gleicher Code und Test:
def test_positive():
assert classify_number(5) == "pos"
Branch-Kanten (Entscheidungsergebnisse) im CFG:
- N1 → N2 (True-Branch von
x > 0) - N1 → N3 (False-Branch von
x > 0) - N3 → N4 (True-Branch von
x < 0) - N3 → N5 (False-Branch von
x < 0)
Gesamte Branch-Kanten: 4
Der Test deckt nur N1 → N2 (True-Branch) ab.
Was ist die Branch Coverage (C1)? Gib eine Zahl ein (ohne %-Zeichen).
Übung 2: Mehrere Testfälle
Gleiche classify_number() Funktion, jetzt mit ZWEI Tests:
def test_positive():
assert classify_number(5) == "pos"
def test_negative():
assert classify_number(-3) == "neg"
Ablaufverfolgungen:
test_positive: START → N1 (True) → N2 → END (deckt N1, N2 ab)test_negative: START → N1 (False) → N3 (True) → N4 → END (deckt N1, N3, N4 ab)
Kombiniert abgedeckte Knoten: N1, N2, N3, N4 (4 von 5)
Was ist die kombinierte Statement Coverage (C0)? Gib eine Zahl ein (ohne %-Zeichen).
Übung 2: Mehrere Testfälle (Fortsetzung)
Von beiden Tests abgedeckte Branch-Kanten:
- N1 → N2 (True): ✓ von test_positive
- N1 → N3 (False): ✓ von test_negative
- N3 → N4 (True): ✓ von test_negative
- N3 → N5 (False): ✗ nicht abgedeckt
Was ist die kombinierte Branch Coverage (C1)? Gib eine Zahl ein (ohne %-Zeichen).
Übung 2: Welcher Test wird für 100% Coverage benötigt?
Gegeben die zwei Tests (test_positive und test_negative), welcher zusätzliche Test würde 100% Branch Coverage erreichen?
Übung 3: calculate_grade() - Mehrere Branches
def calculate_grade(score):
if score >= 90: # N1
return "A" # N2
elif score >= 80: # N3
return "B" # N4
elif score >= 70: # N5
return "C" # N6
elif score >= 60: # N7
return "D" # N8
else:
return "F" # N9
Zwei Tests:
def test_grade_A():
assert calculate_grade(95) == "A" # Pfad: N1(T) → N2
def test_grade_B():
assert calculate_grade(85) == "B" # Pfad: N1(F) → N3(T) → N4
Diese Funktion hat 4 Entscheidungspunkte (N1, N3, N5, N7), jeder mit True/False-Ergebnis. Gesamte Branches: 8
Abgedeckte Branches:
- N1 True: ✓, N1 False: ✓, N3 True: ✓, N3 False: ✗
- N5 True: ✗, N5 False: ✗, N7 True: ✗, N7 False: ✗
Was ist die Branch Coverage (C1)? Gib den Prozentsatz als Dezimalzahl ein (z.B. 37.5).
Übung 3: Wie viele weitere Tests werden benötigt?
Um 100% Branch Coverage (C1) für calculate_grade() zu erreichen, wie viele ZUSÄTZLICHE Tests werden über die zwei bereits vorhandenen (test_grade_A und test_grade_B) hinaus benötigt?
Gib eine Zahl ein.
Übung 4: C0 vs C1 Vergleich
def check_eligibility(age, has_license):
if age >= 18: # N1
if has_license: # N2
return "approved" # N3
return "need license" # N4
return "too young" # N5
Zwei Tests:
def test_approved():
assert check_eligibility(25, True) == "approved"
# Pfad: N1(T) → N2(T) → N3
def test_need_license():
assert check_eligibility(20, False) == "need license"
# Pfad: N1(T) → N2(F) → N4
Coverage-Ergebnisse:
- C0: 4/5 Knoten = 80% (N1, N2, N3, N4 abgedeckt; N5 nicht abgedeckt)
- C1: 3/4 Branches = 75% (N1 True, N2 True, N2 False abgedeckt; N1 False nicht abgedeckt)
Welche Aussage über C0 und C1 ist korrekt?
Übung 5: Verschachtelte Bedingungen - validate_password()
def validate_password(password):
if len(password) < 8: # N1
return "too short" # N2
has_upper = any(c.isupper() for c in password) # N3
has_digit = any(c.isdigit() for c in password) # N4
if not has_upper: # N5
return "need uppercase" # N6
if not has_digit: # N7
return "need digit" # N8
return "valid" # N9
Test:
def test_valid_password():
assert validate_password("SecurePass1") == "valid"
Ablaufverfolgung: “SecurePass1” hat Länge 11, Großbuchstaben und eine Ziffer. Pfad: N1(F) → N3 → N4 → N5(F) → N7(F) → N9
Abgedeckte Knoten: N1, N3, N4, N5, N7, N9 (6 von 9)
Was ist C0? Gib eine Zahl ein (ohne %-Zeichen, auf die nächste ganze Zahl gerundet).
Übung 5: Verschachtelte Bedingungen (Fortsetzung)
Gleicher Test für validate_password():
Entscheidungsergebnisse:
- N1 (len < 8): True = NICHT abgedeckt, False = Abgedeckt ✓
- N5 (not has_upper): True = NICHT abgedeckt, False = Abgedeckt ✓
- N7 (not has_digit): True = NICHT abgedeckt, False = Abgedeckt ✓
Was ist C1? Gib eine Zahl ein (ohne %-Zeichen).
Übung 5: Welche Tests werden für 100% C1 benötigt?
Wähle ALLE Tests aus, die helfen würden, 100% Branch Coverage für validate_password() zu erreichen:
Übung 6: Early Returns - calculate_discount()
def calculate_discount(amount, is_member, coupon_code):
if amount <= 0: # N1
return 0 # N2
discount = 0 # N3
if is_member: # N4
discount += 10 # N5
if coupon_code == "SAVE20": # N6
discount += 20 # N7
elif coupon_code == "SAVE10": # N8
discount += 10 # N9
return amount * (discount / 100) # N10
Zwei Tests:
def test_member_with_save20():
result = calculate_discount(100, True, "SAVE20")
# Pfad: N1(F) → N3 → N4(T) → N5 → N6(T) → N7 → N10
def test_non_member_no_coupon():
result = calculate_discount(100, False, None)
# Pfad: N1(F) → N3 → N4(F) → N6(F) → N8(F) → N10
Abgedeckte Knoten: N1, N3, N4, N5, N6, N7, N8, N10 (8 von 10)
Was ist C0? Gib eine Zahl ein (ohne %-Zeichen).
Übung 6: Welche Branches sind NICHT abgedeckt?
Gleiche Tests wie oben. Wähle ALLE nicht abgedeckten Branches:
Übung 7: Komplexe Bedingungen - approve_loan()
def approve_loan(income, credit_score, has_collateral):
if income < 30000: # N1
return "rejected: low income" # N2
if credit_score < 600: # N3
return "rejected: poor credit" # N4
if income >= 100000 and credit_score >= 750: # N5
return "approved: premium" # N6
if credit_score >= 700 or has_collateral: # N7
return "approved: standard" # N8
return "manual review required" # N9
Drei Tests:
def test_premium_approval():
assert approve_loan(150000, 800, False) == "approved: premium"
# Pfad: N1(F) → N3(F) → N5(T) → N6
def test_standard_with_good_credit():
assert approve_loan(50000, 720, False) == "approved: standard"
# Pfad: N1(F) → N3(F) → N5(F) → N7(T) → N8
def test_standard_with_collateral():
assert approve_loan(50000, 650, True) == "approved: standard"
# Pfad: N1(F) → N3(F) → N5(F) → N7(T) → N8
Abgedeckte Knoten: N1, N3, N5, N6, N7, N8 (6 von 9)
Was ist C0? Gib eine Zahl ein (ohne %-Zeichen, auf die nächste ganze Zahl gerundet).
Übung 7: Komplexe Bedingungen (Fortsetzung)
Branch-Ergebnisse:
- N1 (income < 30k): True = ✗, False = ✓
- N3 (credit < 600): True = ✗, False = ✓
- N5 (income>=100k AND credit>=750): True = ✓, False = ✓
- N7 (credit>=700 OR collateral): True = ✓, False = ✗
Was ist C1? Gib den Prozentsatz als Dezimalzahl ein (z.B. 62.5).
Übung 8: Entwirf deine eigenen Tests
def process_order(total, user_type, promo_code):
if total <= 0: # N1
return "invalid order" # N2
base_discount = 0 # N3
if user_type == "premium": # N4
base_discount = 15 # N5
elif user_type == "member": # N6
base_discount = 5 # N7
if promo_code == "FLASH50": # N8
if total >= 100: # N9
return f"discount: 50%" # N10
return "promo requires $100+" # N11
if base_discount > 0: # N12
return f"discount: {base_discount}%" # N13
return "no discount" # N14
Wie viele Entscheidungspunkte (if/elif Anweisungen) hat dieser Code?
Gib eine Zahl ein.
Übung 8: Minimale Tests für 100% C1
Die process_order() Funktion hat 6 Entscheidungspunkte mit 12 Branches insgesamt.
Was ist die minimale Anzahl an Tests für 100% Branch Coverage (C1)?
Gib eine Zahl ein.
Zusammenfassung: C1 subsumiert C0
Basierend auf den Übungen, welche Aussage über Coverage-Beziehungen ist WAHR?
Zusammenfassung: Wichtigste Erkenntnis aus dem CFG Tracing
Was ist die wichtigste Erkenntnis aus dem Verfolgen von Tests durch Kontrollflussgraphen?