0%

Objektorientierte Programme testen und umstrukturieren · Übung

Mehrere Fälle testen

grade_letter ist eine Treppe:

def grade_letter(fraction):
    if fraction >= 0.9:
        return "A"

    if fraction >= 0.8:
        return "B"
    ...

Fünf Zweige und vier Stellen, an denen sich die Antwort ändert. Ein Test pro Fall ergibt fünf fast identische Funktionen, die sich nur in zwei Werten unterscheiden. Ein einziger Test für alles riskiert, bei einem Fehler nichts Hilfreiches zu sagen.

Es gibt eine Form, die beides verbindet.

Eine Liste von Fällen, eine Schleife

def test_each_band_gets_its_letter():
    cases = [
        (1.0, "A"),
        (0.9, "A"),
        (0.85, "B"),
        (0.6, "D"),
        (0.0, "F"),
    ]

    for fraction, expected in cases:
        assert grade_letter(fraction) == expected

Die Vorbereitung ist die Liste. Jede Zeile ist ein Fall, bestehend aus Eingabe und erwartetem Ergebnis. Einen Fall hinzuzufügen bedeutet, eine Zeile hinzuzufügen.

Kapitel 3 verwendete genau diese Form für Daten. Hier liest sie sich genauso und aus demselben Grund: Wenn sich Dinge nur in ihren Werten unterscheiden, mache die Werte zu Daten.

Der Fehlschlag, der dir nichts sagt

Führe diesen Test gegen ein fehlerhaftes grade_letter aus, und der Bericht sagt:

test_each_band_gets_its_letter failed
assert grade_letter(fraction) == expected

Welcher Anteil? Das lässt sich nicht erkennen. Die Schleife lief eine unbekannte Anzahl von Malen und stoppte irgendwo.

Grundlagen I erwähnte, dass eine Zusicherung eine Meldung enthalten kann. In einer Schleife ist das mehr als eine nette Zugabe:

    for fraction, expected in cases:
        assert grade_letter(fraction) == expected, (
            f"{fraction} should be {expected}, got {grade_letter(fraction)}"
        )

Jetzt nennt der Fehlschlag den Fall:

0.85 should be B, got C

Die allgemeine Regel lautet: Wenn ein Test mehrere Fälle abdeckt, muss die Meldung sagen, welcher Fall betroffen ist. Sonst hast du genau das aufgegeben, was kleine Tests nützlich macht.

Ein Test mit einer Schleife über acht Fälle schlägt fehl. Die Zusicherung hat keine Meldung. Was sagt dir der Bericht?

Fälle an den Grenzen wählen

Fünf Zweige brauchen keine fünfzig Fälle. Sie brauchen die Werte, an denen sich die Antwort ändert, und Werte auf beiden Seiten.

0.9 ist die Grenze zwischen B und A. Sowohl 0.9 als auch 0.89 verdient eine Zeile, denn >= und > unterscheiden sich nur durch einen Tastendruck, und nur einer der Operatoren ist richtig. Ein Fall bei 0.94 sagt dir fast nichts, was 0.9 nicht schon gesagt hätte.

Das folgt derselben Überlegung wie das Testen der richtigen und der falschen Antwort. Fälle rechtfertigen ihren Platz dadurch, dass sie unabhängig fehlschlagen können.

Ebenfalls festhalten: durch nichts teilen

percentage hat einen Zweig, der nichts mit Noten zu tun hat:

def percentage(score, total):
    if total == 0:
        return 0.0

    return score / total

Ohne diese Schutzabfrage würde ein leeres Quiz den Bericht mit ZeroDivisionError abstürzen lassen. Jemand hat sich bewusst für diese Abfrage entschieden. Ein Test sorgt dafür, dass die Entscheidung auch die nächste Person überlebt, die den Zweig für überflüssig hält.

Aufgabe

Schreibe test_scoring.py und decke quizapp.scoring ab.

Importiere grade_letter und percentage und schreibe dann drei Tests:

  • test_each_band_gets_its_letter: Eine Liste von Fällen (fraction, expected_letter), eine Schleife, eine Zusicherung mit einer Meldung, die den Fall nennt. Decke jeden Buchstaben ab und nimm eine Grenze selbst sowie einen Wert knapp darunter auf, beispielsweise 0.9 und 0.89.

  • test_percentage_of_an_empty_quiz_is_zero: percentage(0, 0) gibt 0.0 zurück, statt eine Ausnahme auszulösen.

  • test_percentage_divides_the_score_by_the_total: percentage(2, 5) gibt 0.4 zurück.

Der Test mit der Schleife benötigt mindestens sechs Fälle. Seine Zusicherungsmeldung muss den Anteil enthalten, bei dem der Test fehlgeschlagen ist.