Home

03 Grundlagen des Testens: Coverage Konzepte Quiz

quiz coverage testing c0 c1 subsumption pytest-cov chapter-03

Neu: Interaktives Quiz verfügbar!

Möchtest du deinen Fortschritt verfolgen und sofortiges Feedback erhalten? Probiere unsere neue interaktive Version mit Fortschrittsverfolgung, sofortigen Erklärungen und Punkteberechnung.

Interaktives Quiz starten →

Anleitung

Dieses Quiz testet dein Verständnis der Code Coverage-Konzepte aus den Vorlesungen 6 und 7.


Vorbereitung: Zuerst lesen

Bevor du dieses Quiz versuchst, studiere die folgenden Vorlesungsabschnitte:

Aus Kapitel 03 (TDD und CI): TDD und CI:

Aus Kapitel 03 (Testtheorie und Coverage): Testtheorie & Coverage:

Fokusgebiete: Definitionen, Subsumption-Hierarchie, Coverage-Grenzen


Warum dieses Quiz machen?

Für dein Lernen:

Richtlinien:


Abschnitt A: Definitionen und Grundlagen


Frage 1: Was ist Statement Coverage?

Statement Coverage (C0) misst:

A) Den Prozentsatz der Funktionen, die mindestens einen Test haben
B) Den Prozentsatz der ausführbaren Code-Anweisungen, die von Tests ausgeführt werden
C) Die Anzahl der Testfälle geteilt durch Codezeilen
D) Den Prozentsatz der Branches, die True zurückgeben

Antwort anzeigen

Richtige Antwort: B

Statement Coverage (C0) misst den Prozentsatz der ausführbaren Anweisungen in deinem Code, die während des Testens ausgeführt werden.

Formel:

\(\text{C0} = \frac{\text{Ausgeführte Anweisungen}}{\text{Gesamte Anweisungen}} \times 100\%\)

Beispiel:

def example(x):
    if x > 0:          # Statement 1
        print("pos")   # Statement 2
    return x           # Statement 3

Wenn du mit example(5) testest:

  • Ausgeführte Anweisungen: 1, 2, 3 (alle 3)
  • C0 = 3/3 = 100%

Wenn du mit example(-5) testest:

  • Ausgeführte Anweisungen: 1, 3 (Anweisung 2 übersprungen)
  • C0 = 2/3 = 67%

Warum andere Optionen falsch sind:

  • A: Das wäre “Function Coverage”, nicht Statement Coverage
  • C: Coverage geht um Ausführung, nicht ein einfaches Verhältnis von Tests zu Code
  • D: Das beschreibt Branch-Ergebnisse, nicht Anweisungsausführung

Frage 2: Was ist Branch Coverage?

Branch Coverage (C1) misst:

A) Den Prozentsatz der Code-Branches (True/False-Ergebnisse), die von Tests ausgeführt werden
B) Die Anzahl der if-Anweisungen im Code
C) Den Prozentsatz der Funktionen mit return-Anweisungen
D) Wie oft jede Codezeile ausgeführt wird

Antwort anzeigen

Richtige Antwort: A

Branch Coverage (C1) misst den Prozentsatz der Entscheidungsergebnisse (True/False-Branches), die während des Testens ausgeführt werden.

Formel:

\(\text{C1} = \frac{\text{Ausgeführte Branches}}{\text{Gesamte Branches}} \times 100\%\)

Beispiel:

def example(x):
    if x > 0:          # Decision with 2 branches: True, False
        return "pos"   # True branch
    return "neg"       # False branch (implicit else)

Wenn du mit example(5) testest:

  • True Branch: Ausgeführt
  • False Branch: NICHT ausgeführt
  • C1 = 1/2 = 50%

Um 100% C1 zu erreichen, brauchst du BEIDES:

  • example(5) → True Branch
  • example(-5) → False Branch

Warum andere Optionen falsch sind:

  • B: If-Anweisungen zählen ist keine Coverage; es ist eine Code-Metrik
  • C: Das ist kein Standard-Coverage-Maß
  • D: Das beschreibt “Execution Count”, nicht Branch Coverage

Frage 3: Was ist ein Coverage-Kriterium?

Ein Coverage-Kriterium ist:

A) Ein Werkzeug, das misst, wie schnell Tests laufen
B) Eine Regel, die definiert, was getestet werden muss, um eine Testsuite als angemessen zu betrachten
C) Die minimale Anzahl von Tests, die pro Funktion erforderlich sind
D) Ein Bewertungsschema für die Benotung von Studenten-Code

Antwort anzeigen

Richtige Antwort: B

Ein Coverage-Kriterium ist eine formale Regel, die definiert, welche Elemente eines Programms von einer Testsuite ausgeführt (abgedeckt) werden müssen, damit diese Suite nach diesem Kriterium als “angemessen” gilt.

Beispiele für Coverage-Kriterien:

  • Statement Coverage (C0): Jede Anweisung muss mindestens einmal ausgeführt werden
  • Branch Coverage (C1): Jedes Branch-Ergebnis muss mindestens einmal ausgeführt werden
  • Path Coverage: Jeder mögliche Pfad durch den Code muss ausgeführt werden
  • Condition Coverage: Jeder boolesche Teilausdruck muss True und False sein

Warum das wichtig ist:

Verschiedene Kriterien haben unterschiedliche Stärken (Fehlererkennung) und Kosten (Anzahl benötigter Tests). Die Wahl des richtigen Kriteriums hängt ab von:

  • Risikoniveau des Codes
  • Verfügbare Test-Ressourcen
  • Erforderliches Vertrauensniveau

Warum andere Optionen falsch sind:

  • A: Das ist eine Performance-Metrik, kein Coverage-Kriterium
  • C: Coverage geht darum, was getestet wird, nicht um eine feste Anzahl
  • D: Coverage-Kriterien sind für Test-Angemessenheit, nicht für Benotung

Frage 4: Was bedeutet “Subsumption” beim Testen?

Wenn wir sagen “C1 subsumiert C0,” meinen wir:

A) C1-Tests laufen schneller als C0-Tests
B) Das Erreichen von 100% C1 garantiert automatisch 100% C0
C) C0 ist gründlicher als C1
D) C1 erfordert weniger Tests als C0

Antwort anzeigen

Richtige Antwort: B

Subsumption bedeutet, dass ein Coverage-Kriterium “stärker” ist als ein anderes. Wenn Kriterium A Kriterium B subsumiert, dann garantiert das Erreichen von 100% Coverage unter A automatisch 100% Coverage unter B.

C1 subsumiert C0, weil:

Um beide Branches einer Entscheidung abzudecken:

if condition:
    statement_A   # True branch
else:
    statement_B   # False branch
  • Du MUSST statement_A ausführen (um True Branch abzudecken)
  • Du MUSST statement_B ausführen (um False Branch abzudecken)
  • Daher sind beide Anweisungen abgedeckt

Das Umgekehrte gilt NICHT:

Du kannst 100% C0 ohne 100% C1 haben:

def risky(x, logging=True):
    if logging:
        log_event(x)   # Always executed if logging=True
    return process(x)  # Always executed

Nur mit logging=True testen ergibt:

  • C0: 100% (alle Anweisungen ausgeführt)
  • C1: 50% (nur True Branch von if logging getestet)

Warum andere Optionen falsch sind:

  • A: Subsumption geht um Coverage, nicht Geschwindigkeit
  • C: Das Gegenteil ist wahr; C1 ist gründlicher
  • D: C1 erfordert typischerweise MEHR Tests (muss beide Branches abdecken)

Frage 5: Was ist Test-Angemessenheit?

Eine Testsuite ist “angemessen” nach einem Coverage-Kriterium, wenn:

A) Alle Tests ohne Fehler bestehen
B) Die Testsuite 100% Coverage nach diesem Kriterium erreicht
C) Mindestens 10 Tests für jedes Modul existieren
D) Tests in unter 1 Sekunde laufen

Antwort anzeigen

Richtige Antwort: B

Test-Angemessenheit ist eine Eigenschaft einer Testsuite relativ zu einem spezifischen Coverage-Kriterium. Eine Testsuite ist angemessen, wenn sie 100% Coverage nach dem gewählten Kriterium erreicht.

Beispiele:

  • C0-angemessen: Jede Anweisung wird von mindestens einem Test ausgeführt
  • C1-angemessen: Jedes Branch-Ergebnis wird von mindestens einem Test ausgeführt

Wichtige Unterscheidung:

  • Angemessen bedeutet “erfüllt die Anforderungen des Kriteriums”
  • Angemessen bedeutet NICHT “Tests sind korrekt” oder “Code ist fehlerfrei”

Eine Testsuite kann C0-angemessen sein und trotzdem:

  • Schwache Assertions haben
  • Grenzfälle verpassen
  • Tatsächliche Bugs nicht erkennen

Warum andere Optionen falsch sind:

  • A: Tests bestehen geht um Korrektheit, nicht Angemessenheit
  • C: Angemessenheit geht nicht um Anzahl; es geht um Coverage
  • D: Geschwindigkeit ist ein Performance-Thema, nicht Angemessenheit

Abschnitt B: Verständnis und Berechnung


Frage 6: Warum subsumiert C1 C0?

Betrachte diesen Code:

def check(value):
    if value > 0:
        return "positive"
    else:
        return "negative"

Warum gibt das Erreichen von 100% C1 automatisch 100% C0?

A) Weil C1 mehr Anweisungen zählt als C0
B) Weil das Abdecken beider Branches erfordert, alle Anweisungen in diesen Branches auszuführen
C) Weil C1-Tests länger sind als C0-Tests
D) Weil Branch Coverage nach Statement Coverage berechnet wird

Antwort anzeigen

Richtige Antwort: B

Um 100% C1 bei diesem Code zu erreichen, musst du abdecken:

  1. True Branch (value > 0 ist True) → Führt return "positive" aus
  2. False Branch (value > 0 ist False) → Führt return "negative" aus

Durch das Abdecken beider Branches hast du notwendigerweise ausgeführt:

  • Zeile 2: if value > 0: (die Bedingung selbst)
  • Zeile 3: return "positive" (True Branch Anweisung)
  • Zeile 5: return "negative" (False Branch Anweisung)

Alle 3 ausführbaren Anweisungen sind abgedeckt, also C0 = 100%.

Visueller Beweis:

Test Abgedeckter Branch Ausgeführte Anweisungen
check(5) True Zeilen 2, 3
check(-5) False Zeilen 2, 5
Kombiniert 100% C1 Alle Anweisungen (100% C0)

Warum andere Optionen falsch sind:

  • A: C1 “zählt” keine Anweisungen; es zählt Branch-Ergebnisse
  • C: Testlänge hat nichts mit Subsumption zu tun
  • D: Die Reihenfolge der Berechnung beeinflusst die Beziehung nicht

Frage 7: Statement Coverage berechnen

Gegeben dieser Code und Test:

def categorize(score):
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    else:
        return "F"

# Test:
def test_high_score():
    assert categorize(95) == "A"

Was ist die Statement Coverage (C0)?

A) 25%
B) 40%
C) 50%
D) 100%

Selbst ausprobieren

Du kannst deine Antwort mit pytest-cov überprüfen! Erstelle diese Dateien:

src/grading.py:

def categorize(score):
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    else:
        return "F"

tests/test_grading.py:

from src.grading import categorize

def test_high_score():
    assert categorize(95) == "A"

Coverage ausführen:

uv run pytest tests/ --cov=src --cov-report=term-missing

Voraussetzungen: Python 3.12+, uv Package Manager, pytest und pytest-cov installiert (uv add pytest pytest-cov).

Antwort anzeigen

Richtige Antwort: B

Zählen wir die ausführbaren Anweisungen:

def categorize(score):
    if score >= 90:      # Statement 1 (condition check)
        return "A"       # Statement 2 (executed)
    elif score >= 80:    # Statement 3 (NOT executed - short-circuit)
        return "B"       # Statement 4 (NOT executed)
    elif score >= 70:    # Statement 5 (NOT executed)
        return "C"       # Statement 6 (NOT executed)
    else:
        return "F"       # Statement 7 (NOT executed)

Mit categorize(95):

  • Anweisung 1: Ausgeführt (Bedingung ist True)
  • Anweisung 2: Ausgeführt (gibt “A” zurück)
  • Anweisungen 3-7: NICHT ausgeführt (Funktion kehrt früh zurück)

Berechnung:

  • Ausgeführt: 2 Anweisungen
  • Gesamt: 5 Anweisungen (die 4 returns + der Funktionseintritt/erste Bedingung)
  • C0 = 2/5 = 40%

Hinweis: Genaue Zahlen können je nachdem variieren, wie dein Coverage-Tool Anweisungen zählt (manche zählen if und elif separat, manche nicht). Die Kernaussage ist, dass nur ein kleiner Teil des Codes ausgeführt wird.

Warum andere Optionen falsch sind:

  • A (25%): Zu niedrig; wir haben mehr als 1 von 4 ausgeführt
  • C (50%): Wir haben nicht die Hälfte der Anweisungen ausgeführt
  • D (100%): Wir haben eindeutig die B, C und F Branches übersprungen

Frage 8: Branch Coverage berechnen

Mit dem gleichen Code aus Frage 7:

def categorize(score):
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    else:
        return "F"

# Tests:
def test_scores():
    assert categorize(95) == "A"
    assert categorize(85) == "B"

Was ist die Branch Coverage (C1)?

A) 25%
B) 50%
C) 75%
D) 100%

Selbst ausprobieren

Überprüfe deine Antwort mit pytest-cov mit aktivierter Branch Coverage:

tests/test_grading.py:

from src.grading import categorize

def test_scores():
    assert categorize(95) == "A"
    assert categorize(85) == "B"

Mit Branch Coverage ausführen:

uv run pytest tests/ --cov=src --cov-branch --cov-report=term-missing

Das --cov-branch Flag aktiviert Branch Coverage Reporting. Achte auf die “Branch” und “BrPart” Spalten in der Ausgabe.

Antwort anzeigen

Richtige Antwort: B

Identifizieren wir alle Branches:

if score >= 90:      # Branch 1 (True), Branch 2 (False)
    return "A"
elif score >= 80:    # Branch 3 (True), Branch 4 (False)
    return "B"
elif score >= 70:    # Branch 5 (True), Branch 6 (False)
    return "C"
else:
    return "F"

Gesamte Branches: 6 (jede Bedingung hat True und False Ergebnisse)

Mit unseren Tests:

Test score >= 90 score >= 80 score >= 70
categorize(95) True (Branch 1) - -
categorize(85) False (Branch 2) True (Branch 3) -

Abgedeckte Branches: 1, 2, 3 = 3 Branches NICHT abgedeckte Branches: 4, 5, 6 (brauchen Scores wie 75 und 50)

Berechnung:

C1 = 3/6 = 50%

Um 100% C1 zu erreichen, bräuchten wir:

  • categorize(95) → A (deckt score >= 90 True ab)
  • categorize(85) → B (deckt score >= 90 False, score >= 80 True ab)
  • categorize(75) → C (deckt score >= 80 False, score >= 70 True ab)
  • categorize(50) → F (deckt score >= 70 False ab)

Frage 9: Coverage Report interpretieren

Gegeben diese pytest-cov Ausgabe:

Name              Stmts   Miss  Cover   Missing
-----------------------------------------------
src/utils.py         24      6    75%   18-20, 35-37
-----------------------------------------------

Was sagt dir “Missing: 18-20, 35-37”?

A) Zeilen 18-20 und 35-37 haben Syntaxfehler
B) Zeilen 18-20 und 35-37 wurden von keinem Test ausgeführt
C) Zeilen 18-20 und 35-37 sollten gelöscht werden
D) Zeilen 18-20 und 35-37 enthalten den wichtigsten Code

Antwort anzeigen

Richtige Antwort: B

Die “Missing” Spalte zeigt Zeilennummern, die von keinem Test in deiner Testsuite nicht ausgeführt wurden.

Was das bedeutet:

  • Zeilen 18, 19, 20: Nicht von Tests abgedeckt
  • Zeilen 35, 36, 37: Nicht von Tests abgedeckt
  • Diese Zeilen repräsentieren ungetestete Codepfade

Was dagegen zu tun ist:

  1. Schau dir den Code an diesen Zeilen an
  2. Verstehe warum sie nicht ausgeführt wurden:
    • Ist es ein bedingter Branch, der nicht ausgelöst wurde?
    • Ist es Fehlerbehandlungscode?
    • Ist es toter Code, der entfernt werden sollte?
  3. Füge Tests hinzu, die diese Zeilen ausführen (wenn es gültiger Code ist)

Beispielszenario:

# Line 18-20 might be:
if special_case:
    handle_special()
    return early_result

# Your tests never triggered special_case = True

Warum andere Optionen falsch sind:

  • A: Coverage-Tools erkennen keine Syntaxfehler; sie messen Ausführung
  • C: Fehlende Zeilen sind nicht unbedingt schlecht; sie brauchen nur Tests
  • D: Die Wichtigkeit von Code hängt nicht davon ab, ob er getestet ist

Frage 10: Report mit Code abgleichen

Gegeben dieser Code:

1
2
3
4
5
6
7
8
def validate(value):
    if value is None:
        return "Error"
    if value < 0:
        return "Negative"
    if value == 0:
        return "Zero"
    return "Positive"

Und dieser Coverage Report:

Missing: 3, 7

Welche Bedingungen waren NIEMALS False während des Testens?

A) value is None und value < 0
B) value is None und value == 0
C) value < 0 und value == 0
D) Alle Bedingungen wurden als True und False getestet

Antwort anzeigen

Richtige Antwort: B

Analysieren wir, was “Missing: 3, 7” bedeutet:

  • Zeile 3 fehlt: return "Error" wurde nie ausgeführt
    • Das bedeutet value is None war nie True
    • Also war value is None immer False
  • Zeile 7 fehlt: return "Zero" wurde nie ausgeführt
    • Das bedeutet value == 0 war nie True
    • Also war value == 0 immer False

Was wahrscheinlich getestet wurde:

  • validate(5) → Positive (Zeilen 2, 4, 6, 8 ausgeführt)
  • validate(-3) → Negative (Zeilen 2, 4, 5 ausgeführt)

Was NICHT getestet wurde:

  • validate(None) → Würde Zeile 3 ausführen
  • validate(0) → Würde Zeile 7 ausführen

Warum andere Optionen falsch sind:

  • A: Zeile 5 (return "Negative") fehlt NICHT, also war value < 0 irgendwann True
  • C: Zeile 5 fehlt NICHT, also wurde value < 0 abgedeckt
  • D: Zeilen 3 und 7 fehlen, also wurden nicht alle Bedingungen vollständig getestet

Abschnitt C: Grenzen von Coverage


Frage 11: Warum garantiert 100% Coverage keinen fehlerfreien Code?

Eine Testsuite erreicht 100% Statement Coverage. Das bedeutet:

A) Der Code ist definitiv korrekt und produktionsreif
B) Alle möglichen Eingaben wurden getestet
C) Jede Anweisung wurde ausgeführt, aber die Tests könnten schwache oder fehlende Assertions haben
D) Der Code hat keine Bugs

Antwort anzeigen

Richtige Antwort: C

100% Coverage bedeutet, dass jede Anweisung ausgeführt wurde, aber sagt nichts darüber aus:

  • Ob Assertions korrektes Verhalten verifizieren
  • Ob alle wichtigen Eingaben getestet wurden
  • Ob die Logik tatsächlich korrekt ist

Beispiel für 100% Coverage ohne Bug-Erkennung:

def divide(a, b):
    return a / b   # Bug: doesn't handle b=0

def test_divide():
    result = divide(10, 2)
    assert result is not None  # Weak assertion! Doesn't check value

Dies erreicht 100% Coverage, aber:

  • Verifiziert nicht das tatsächliche Ergebnis (sollte 5 sein)
  • Testet b=0 nicht (was abstürzen würde)
  • Gibt falsches Vertrauen

Was Coverage dir SAGT:

  • Welcher Code überhaupt nicht ausgeführt wurde (definitiv nicht getestet)
  • Lücken in deiner Testsuite

Was Coverage dir NICHT sagt:

  • Ob Tests tatsächlich Korrektheit verifizieren
  • Ob Grenzfälle behandelt werden
  • Ob der Code fehlerfrei ist

Warum andere Optionen falsch sind:

  • A: Korrektheit erfordert richtige Assertions, nicht nur Ausführung
  • B: Du kannst 100% Coverage mit sehr wenigen Eingaben erreichen
  • D: Coverage misst Ausführung, nicht Korrektheit

Frage 12: Schlechter Test mit hoher Coverage

Betrachte diesen Code und Test:

def calculate_total(items, discount_percent):
    total = sum(items)
    if discount_percent > 0:
        discount = total * (discount_percent / 100)
        total = total - discount
    return total

def test_calculate_total():
    result = calculate_total([10, 20, 30], 10)
    # What's wrong with this test?
    assert result > 0

Was ist das Problem mit diesem Test?

A) Er erreicht keine 100% Coverage
B) Er verwendet die falsche Assertion-Syntax
C) Die Assertion ist zu schwach - sie verifiziert nicht den korrekten Wert
D) Der Testname ist nicht aussagekräftig genug

Antwort anzeigen

Richtige Antwort: C

Der Test erreicht hohe Coverage durch Ausführen von:

  • total = sum(items) → 60
  • if discount_percent > 0: → True
  • discount = total * (discount_percent / 100) → 6
  • total = total - discount → 54
  • return total → gibt 54 zurück

Aber die Assertion assert result > 0 ist extrem schwach:

  • Sie würde für result = 1 bestehen (falsch!)
  • Sie würde für result = 100000 bestehen (falsch!)
  • Sie würde für result = 54 bestehen (korrekt)

Ein richtiger Test sollte den exakt erwarteten Wert verifizieren:

def test_calculate_total_with_discount():
    # Arrange
    items = [10, 20, 30]
    discount_percent = 10

    # Act
    result = calculate_total(items, discount_percent)

    # Assert - verify the exact expected value
    expected = 54  # (10+20+30) - 10% = 60 - 6 = 54
    assert result == expected

Kernaussage:

Coverage sagt dir, dass der Code gelaufen ist, aber nur gute Assertions sagen dir, dass der Code korrekt funktioniert hat.

Warum andere Optionen falsch sind:

  • A: Der Test erreicht wahrscheinlich gute Coverage (alle Zeilen ausgeführt)
  • B: Die Syntax ist gültiges Python
  • D: Obwohl wahr, ist die schwache Assertion das kritische Problem

Frage 13: Was kann Coverage NICHT erkennen?

Welchen der folgenden Bugs würde 100% Statement Coverage wahrscheinlich VERPASSEN?

A) Eine fehlende Import-Anweisung
B) Eine falsche Berechnungsformel
C) Einen Syntaxfehler im Code
D) Eine undefinierte Variable

Antwort anzeigen

Richtige Antwort: B

Eine falsche Berechnungsformel könnte trotzdem perfekt ausgeführt werden und 100% Coverage erreichen, während sie falsche Ergebnisse produziert.

Beispiel:

def calculate_area(radius):
    return 3.14 * radius * radius  # Bug: should use math.pi

def test_area():
    result = calculate_area(10)
    assert result is not None  # Passes! But result is slightly wrong

Dies erreicht 100% Coverage, aber:

  • Die Formel verwendet 3.14 statt math.pi
  • Das Ergebnis ist ungefähr 314 statt 314.159…
  • Eine schwache Assertion fängt das nicht ab

Was Coverage (indirekt) erkennen KANN:

  • A (fehlender Import): Würde abstürzen, wenn dieses Modul verwendet wird
  • C (Syntaxfehler): Python läuft gar nicht
  • D (undefinierte Variable): Würde NameError auslösen, wenn ausgeführt

Kernaussage:

Coverage erkennt Abstürze (Code, der nicht läuft), aber nicht logische Fehler (Code, der läuft, aber falsche Ausgaben produziert).

Deshalb brauchst du:

  1. Gute Coverage (Code wird ausgeführt)
  2. Starke Assertions (Verhalten wird verifiziert)
  3. Grenzfall-Tests (Randbedingungen werden geprüft)

Frage 14: Wann ist 70% Coverage akzeptabel?

In welcher Situation könnte 70% Coverage völlig akzeptabel sein?

A) Niemals - strebe immer 100% an
B) Wenn die nicht abgedeckten 30% toter Code oder unerreichbare Fehlerbehandler sind
C) Wenn Tests zu teuer zu schreiben sind
D) Wenn das Projekt fast fertig ist

Antwort anzeigen

Richtige Antwort: B

70% Coverage kann akzeptabel sein, wenn der nicht abgedeckte Code ist:

  1. Toter Code, der entfernt werden sollte
  2. Defensive Fehlerbehandler für “unmögliche” Bedingungen
  3. Plattformspezifischer Code, der für die aktuelle Umgebung nicht relevant ist
  4. Debug/Logging-Code, der nicht kritisch zu testen ist

Beispiele für akzeptablen nicht abgedeckten Code:

def process_file(path):
    try:
        with open(path) as f:
            return f.read()
    except PermissionError:
        # Only happens in restricted environments
        log_error("Permission denied")
        return None
    except Exception as e:
        # Defensive catch-all, should "never" happen
        log_critical(f"Unexpected: {e}")
        raise

Die except-Blöcke könnten nicht abgedeckt sein, wenn deine Tests nur zugängliche Dateien verwenden. Das ist oft OK.

Wann 70% NICHT akzeptabel ist:

  • Kern-Geschäftslogik ist nicht abgedeckt
  • Happy-Path-Funktionalität ist nicht getestet
  • Die 30% enthalten kritischen Code

Warum andere Optionen falsch sind:

  • A: 100% ist nicht immer praktikabel oder sinnvoll
  • C: Kosten allein sind kein guter Grund; priorisiere kritischen Code
  • D: Projektphase bestimmt nicht akzeptable Coverage

Frage 15: Coverage vs Korrektheit

Welche Aussage beschreibt am besten die Beziehung zwischen Coverage und Korrektheit?

A) Hohe Coverage beweist Code-Korrektheit
B) Coverage misst Ausführung; Korrektheit erfordert Assertions und Testdesign
C) Niedrige Coverage beweist, dass Code inkorrekt ist
D) Coverage und Korrektheit sind dasselbe

Antwort anzeigen

Richtige Antwort: B

Coverage und Korrektheit sind verschiedene Konzepte:

Aspekt Coverage Korrektheit
Misst Code-Ausführung Verhaltensverifikation
Werkzeug pytest-cov Assertions
Frage “Wurde dieser Code ausgeführt?” “Funktioniert dieser Code richtig?”
Automatisierbar Ja (vollständig) Teilweise (Assertions brauchen menschliches Design)

Hohe Coverage + schwache Assertions = Falsches Vertrauen

def test_broken():
    result = buggy_function()  # Runs the code (coverage!)
    assert result is not None   # Doesn't check correctness

Niedrige Coverage + starke Assertions = Unvollständiges Testen

def test_one_path():
    assert calculate(10) == 100  # Correct for this input
    # But what about calculate(-5)? calculate(0)?

Ideal: Hohe Coverage + starke Assertions + gutes Testdesign

def test_positive():
    assert calculate(10) == 100

def test_negative():
    assert calculate(-5) == -50

def test_zero():
    assert calculate(0) == 0

def test_edge_case():
    assert calculate(0.001) == 0.01

Warum andere Optionen falsch sind:

  • A: Coverage beweist Ausführung, nicht Korrektheit
  • C: Niedrige Coverage bedeutet ungetesteten Code, nicht inkorrekten Code
  • D: Sie sind komplementäre, aber unterschiedliche Konzepte

Abschnitt D: Anwendung


Frage 16: Welcher Test würde Zeile X abdecken?

Gegeben dieser Code:

1
2
3
4
5
6
7
8
9
def classify_age(age):
    if age < 0:
        return "Invalid"
    elif age < 18:
        return "Minor"
    elif age < 65:
        return "Adult"
    else:
        return "Senior"

Dein Coverage Report zeigt, dass Zeile 9 fehlt. Welcher Test würde sie abdecken?

A) assert classify_age(-5) == "Invalid"
B) assert classify_age(17) == "Minor"
C) assert classify_age(30) == "Adult"
D) assert classify_age(70) == "Senior"

Selbst ausprobieren

Teste deine Hypothese! Erstelle diese Dateien und führe Coverage aus:

src/age_classifier.py:

def classify_age(age):
    if age < 0:
        return "Invalid"
    elif age < 18:
        return "Minor"
    elif age < 65:
        return "Adult"
    else:
        return "Senior"

tests/test_age.py (initiale Tests, die Zeile 9 nicht abdecken):

from src.age_classifier import classify_age

def test_ages():
    assert classify_age(-5) == "Invalid"
    assert classify_age(17) == "Minor"
    assert classify_age(30) == "Adult"

Coverage ausführen, um Zeile 9 als fehlend zu sehen:

uv run pytest tests/ --cov=src --cov-report=term-missing

Dann füge deinen gewählten Test hinzu und führe erneut aus, um zu verifizieren, dass er Zeile 9 abdeckt!

Antwort anzeigen

Richtige Antwort: D

Zeile 9 (return "Senior") ist im else-Branch, der ausgeführt wird, wenn:

  • age < 0 ist False (age ist 0 oder positiv)
  • age < 18 ist False (age ist 18 oder älter)
  • age < 65 ist False (age ist 65 oder älter)

Also brauchen wir age >= 65, um Zeile 9 zu erreichen.

Analyse jeder Option:

Test age Zeile 3? Zeile 5? Zeile 7? Zeile 9?
A: -5 -5 Ja Nein Nein Nein
B: 17 17 Nein Ja Nein Nein
C: 30 30 Nein Nein Ja Nein
D: 70 70 Nein Nein Nein Ja

Nur Option D deckt Zeile 9 ab.

Warum andere Optionen falsch sind:

  • A: Deckt Zeile 3 ab (age < 0 ist True)
  • B: Deckt Zeile 5 ab (age < 18 ist True)
  • C: Deckt Zeile 7 ab (age < 65 ist True)

Frage 17: Test für nicht abgedeckten Branch entwerfen

Gegeben dieser Code und Coverage Report:

1
2
3
4
5
6
7
8
9
def calculate_shipping(weight, express=False):
    if weight <= 0:
        raise ValueError("Invalid")

    base_cost = weight * 2.0

    if express:
        return base_cost * 1.5
    return base_cost

Coverage Report: Fehlende Zeilen 3, 8

Um BEIDE fehlenden Zeilen abzudecken, brauchst du Tests mit welchen Eingaben?

A) weight=5, express=True nur
B) weight=-1 und weight=5, express=True
C) weight=0 und weight=5, express=False
D) weight=5, express=True und weight=10, express=True

Antwort anzeigen

Richtige Antwort: B

Verfolgen wir, was jede fehlende Zeile abdeckt:

Zeile 3 (raise ValueError): Braucht weight <= 0

  • weight = -1 → Zeile 2 ist True → Zeile 3 wird ausgeführt

Zeile 8 (return base_cost * 1.5): Braucht weight > 0 UND express=True

  • weight = 5, express = True → Erreicht Zeile 7, Zeile 7 ist True → Zeile 8 wird ausgeführt

Analyse der Optionen:

Option Deckt Zeile 3 ab? Deckt Zeile 8 ab?
A Nein (weight > 0) Ja
B Ja (weight = -1) Ja (weight=5, express=True)
C Ja (weight = 0) Nein (express=False)
D Nein Ja (beide sind express=True)

Nur Option B deckt BEIDE fehlenden Zeilen ab.

Warum andere Optionen falsch sind:

  • A: Deckt nur Zeile 8 ab, nicht Zeile 3
  • C: Deckt nur Zeile 3 ab, nicht Zeile 8
  • D: Beide Tests haben positives weight, decken nie Zeile 3 ab

Frage 18: pytest-cov Ausgabe interpretieren

Gegeben diese pytest-cov Ausgabe mit aktivierter Branch Coverage:

Name              Stmts   Miss Branch BrPart  Cover
----------------------------------------------------
src/validator.py     20      2     10      3    82%
----------------------------------------------------

Was bedeutet “BrPart: 3”?

A) 3 Branches wurden getestet
B) 3 Branches sind teilweise abgedeckt (nur ein Ergebnis getestet)
C) 3 Branches sollten gelöscht werden
D) 3 Branches haben Fehler

Antwort anzeigen

Richtige Antwort: B

BrPart steht für “Branch Partial” - es zählt Branches, bei denen nur ein Ergebnis (True ODER False) getestet wurde, aber nicht beide.

Beispiel für teilweise Branch Coverage:

def process(value, debug=True):
    if debug:           # Branch: tested only when debug=True
        log(value)
    return transform(value)

Wenn alle Tests debug=True verwenden:

  • True Branch: Abgedeckt (log wird aufgerufen)
  • False Branch: NICHT abgedeckt (log wird übersprungen)
  • Dieser Branch ist “teilweise abgedeckt” → trägt zu BrPart bei

Spaltenbedeutungen:

  • Stmts: Gesamte Anweisungen (20)
  • Miss: Nicht ausgeführte Anweisungen (2)
  • Branch: Gesamte Branch-Ergebnisse (10)
  • BrPart: Teilweise abgedeckte Branches (3)
  • Cover: Gesamter Coverage-Prozentsatz (82%)

Um teilweise Branches zu beheben:

Füge Tests hinzu, die das gegenteilige Bedingungsergebnis auslösen.

Warum andere Optionen falsch sind:

  • A: BrPart zählt unvollständige Branches, nicht vollständige
  • C: Teilweise Coverage ist kein Grund, Code zu löschen
  • D: Dies ist eine Coverage-Metrik, kein Fehlerbericht

Frage 19: Angemessenen Coverage-Schwellenwert wählen

Dein Team richtet eine CI-Pipeline für ein Finanztransaktionssystem ein. Welcher Coverage-Schwellenwert ist am angemessensten?

A) 50% - Niedriger Schwellenwert, um Deployments nicht zu blockieren
B) 70% - Industriestandard
C) 85-90% - Hoher Schwellenwert mit Fokus auf kritische Pfade
D) 100% - Finanzsysteme brauchen vollständige Coverage

Antwort anzeigen

Richtige Antwort: C

Für ein Finanztransaktionssystem ist hohe Coverage wichtig, weil:

  • Bugs können finanzielle Verluste verursachen
  • Grenzfälle bei Geldhandhabung sind kritisch
  • Regulatorische Anforderungen können Tests vorschreiben

Warum 85-90% statt 100%:

  • 100% ist oft unpraktisch (Fehlerbehandler, Debug-Code)
  • 100% kann dazu führen, Implementierungsdetails zu testen
  • Fokus sollte auf sinnvoller Coverage kritischer Pfade liegen

Best Practice für kritische Systeme:

  1. Basis-Schwellenwert setzen (85-90%)
  2. 100% bei kritischen Modulen erfordern (z.B. transactions.py)
  3. Niedrigere Coverage bei nicht-kritischen Hilfsfunktionen erlauben
  4. Starke Assertions durchsetzen, nicht nur Ausführung

Beispiel CI-Konfiguration:

- name: Run tests with coverage
  run: pytest --cov=src --cov-fail-under=85 --cov-report=term-missing

Warum andere Optionen falsch sind:

  • A (50%): Zu niedrig für Finanzsysteme
  • B (70%): Industriestandard für allgemeine Apps, aber Finanzen braucht mehr
  • D (100%): Unpraktisch und kann dazu führen, die Metrik zu manipulieren

Frage 20: Zweck der CI-Integration

Warum Coverage-Checks in eine CI/CD-Pipeline integrieren?

A) Um Bugs im Code automatisch zu beheben
B) Um zu verhindern, dass Code gemerged wird, der die Test-Coverage unter den Schwellenwert senkt
C) Um Tests schneller zu machen
D) Um manuelles Code-Review zu ersetzen

Antwort anzeigen

Richtige Antwort: B

CI Coverage-Integration dient als Qualitäts-Gate, das:

  1. Coverage bei jedem Pull Request misst
  2. Den Build fehlschlagen lässt, wenn Coverage unter den Schwellenwert fällt
  3. Verhindert, dass sich “Coverage-Schulden” ansammeln
  4. Coverage für das Team sichtbar macht

Wie es funktioniert:

# In GitHub Actions workflow
- name: Run tests with coverage
  run: uv run pytest --cov=src --cov-fail-under=80

# If coverage < 80%, the workflow fails
# PR cannot be merged until coverage is restored

Vorteile:

  • Automatische Durchsetzung: Keine manuelle Überprüfung nötig
  • Frühes Feedback: Entwickler wissen sofort, ob sie mehr Tests brauchen
  • Trend-Schutz: Coverage kann nicht stillschweigend abnehmen
  • Team-Sichtbarkeit: Jeder sieht Coverage in PR-Checks

Was CI Coverage NICHT macht:

  • Bugs automatisch beheben
  • Tests beschleunigen
  • Code-Review ersetzen (Menschen werden noch gebraucht)

Warum andere Optionen falsch sind:

  • A: CI misst; es behebt nicht
  • C: Coverage-Checks fügen Zeit hinzu, reduzieren sie nicht
  • D: Code-Review fängt Design-Probleme, die Coverage nicht kann

Bewertungsleitfaden


Wichtige Erkenntnisse zum Merken

  1. Statement Coverage (C0) misst Prozentsatz der ausgeführten Anweisungen
  2. Branch Coverage (C1) misst Prozentsatz der ausgeführten Branch-Ergebnisse (True/False)
  3. C1 subsumiert C0: 100% Branch Coverage garantiert 100% Statement Coverage
  4. Coverage-Kriterium definiert, was eine Testsuite “angemessen” macht
  5. 100% Coverage ≠ fehlerfrei: Tests können Code ausführen, ohne ihn zu verifizieren
  6. Schwache Assertions sind der Feind: assert x is not None fängt nichts ab
  7. Coverage zeigt Lücken: “Missing” Zeilen sagen dir, was NICHT getestet ist
  8. Teilweise Branches (BrPart) bedeuten, nur ein Ergebnis wurde getestet
  9. 70-80% ist akzeptabel für die meisten Projekte; kritische Systeme brauchen mehr
  10. CI-Integration verhindert Coverage-Regression

Was kommt als Nächstes?

Fahre mit der CFG Tracing Übung fort, um zu üben, Coverage visuell mit Kontrollflussgraphen zu analysieren.

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk