Objektorientierte Programme testen und umstrukturieren · Übung
Verhalten mit Regressionstests schützen
Eine Regression ist Verhalten, das früher richtig war und unbemerkt aufgehört hat, richtig zu sein. Ein Regressionstest ist der Test, der das erkannt hätte.
Solche Tests gelangen auf zwei Wegen in ein Programm.
Der erste ist eine Fehlermeldung. Etwas war falsch, du hast die Ursache verstanden, und vor der Korrektur hast du einen Test geschrieben, der genau deshalb fehlschlug. Jetzt bewacht der Test die Korrektur. Er schlägt wieder fehl, sobald jemand den Fehler erneut einführt.
Der zweite Weg ist der, den dieses Kapitel braucht. Du wirst Code ändern, der derzeit funktioniert, und möchtest erkennen, wenn du ihn beschädigst.
Vor der Änderung festhalten, was der Code tut
Die nächste Lektion findet ein Entwurfsproblem in report.py. Die darauffolgende behebt es, indem sie eine funktionierende Funktion umschreibt.
Beim Umschreiben von funktionierendem Code geht Verhalten verloren. Nicht das Verhalten, an das du gerade denkst und das du von Hand prüfst. Sondern das andere: das leere Quiz, die volle Punktzahl, die exakten Leerzeichen in einer Zeile, die anderer Code einliest.
Halte deshalb zuerst fest, was der Code heute tut:
def test_the_full_report_for_the_chapter_quiz():
attempt = build_chapter_attempt()
lines = report_lines(attempt)
assert lines == ["Mina: 2 of 5", "Grade: F"]
Dieser Test behauptet nicht, dass die aktuelle Ausgabe gut ist. Er zeichnet nur auf, was das Programm gerade erzeugt. Das reicht, um ein Refactoring sicher zu machen. Deshalb kannst du den Test schreiben, bevor du weißt, wie der neue Entwurf aussehen wird.
Tests für diesen Zweck heißen manchmal Charakterisierungstests, weil sie beschreiben, was der Code tut, statt festzulegen, was er tun sollte.
Fälle abdecken, die du nicht von Hand prüfen würdest
Beim offensichtlichen Fall fällt dir auf, wenn er nicht mehr funktioniert. Die Randfälle verschwinden unbemerkt:
def test_an_empty_quiz_still_reports():
quiz = Quiz("Empty")
attempt = Attempt(quiz, "Mina")
lines = report_lines(attempt)
assert lines == ["Mina: 0 of 0", "Grade: F"]
An dieser Situation ist nichts interessant, bis jemand report_lines umschreibt und den Anteil berechnet, bevor geprüft wird, ob es überhaupt etwas gibt, durch das geteilt werden kann. Dann stürzt das Programm für jemanden mit einem leeren Quiz ab, in einem Fall, den niemand ausprobiert hat.
Lektion 5 prüfte, dass percentage davor schützt. Dieser Test macht eine andere Aussage: Der Schutz ist vom Bericht aus weiterhin erreichbar. Beide Aussagen können stimmen, dann hört eine auf zu stimmen, und nur dieser Test merkt es.
Du wirst eine Funktion umstrukturieren, deren aktuelle Ausgabe du für leicht fehlerhaft hältst. Was sollte der Regressionstest prüfen?
Nicht mehr festhalten als beabsichtigt
Ein Regressionstest kann zu weit gehen. Prüfst du den exakten Text jeder Zeile, frierst du den Wortlaut ein. Eine berechtigte Verbesserung des Berichts „scheitert“ dann, und jemand muss entscheiden, ob der Test oder die Änderung falsch ist.
Dafür gibt es keine Formel, nur eine hilfreiche Frage: Wenn dieser Test eines Tages fehlschlägt, bin ich dann froh oder genervt? Froh bedeutet, dass er etwas geschützt hat. Genervt bedeutet, dass er etwas Nebensächliches beschrieben hat.
Die beiden Zeilen von report_lines verdienen es, exakt festgehalten zu werden. Sie sind die Ausgabe des Programms, und die nächsten beiden Lektionen werden den Code verschieben, der sie erstellt.
Aufgabe
Schreibe test_report_output.py und halte exakt fest, was der Bericht heute erzeugt.
Importiere das Benötigte aus quizapp.models und report und schreibe dann drei Tests:
test_the_full_report_for_the_chapter_quiz: Mina erzielt 2 von 5 Punkten. Die gesamte Liste lautet["Mina: 2 of 5", "Grade: F"].test_a_perfect_attempt_reports_an_a: Mina beantwortet beide Fragen richtig. Die Liste lautet["Mina: 5 of 5", "Grade: A"].test_an_empty_quiz_still_reports: Ein Quiz ganz ohne Fragen erzeugt["Mina: 0 of 0", "Grade: F"]und löst keine Ausnahme aus.
Prüfe die gesamte Liste auf einmal statt Zeile für Zeile. Diese Tests sind das Sicherheitsnetz für das Refactoring in Lektion 9. Sie sollen deshalb deutlich fehlschlagen, wenn sich irgendein Teil der Ausgabe verändert.