Kapitel 13 · Übung
Objektorientierte Programme testen und umstrukturieren
Öffentliches Verhalten testen
In Grundlagen I endete ein Kapitel mit einzelnen Zusicherungen unter dem Code, den sie prüften:
assert double(4) == 8
Das funktioniert und bleibt der Kern dessen, was du gleich tun wirst. Es fehlt nur ein fester Platz dafür. Zusicherungen unter dem Code laufen bei jedem Programmstart, verschwinden beim Aufräumen und halten bei der ersten fehlgeschlagenen Prüfung an.
Ein Test ist dieselbe Zusicherung mit einem Namen und einem festen Platz.
Ein Test ist eine Funktion, deren Name mit test_ beginnt
def test_a_correct_response_scores():
question = Question("Which keyword starts a function?", "def", 2)
assert question.is_correct("def")
Das ist die gesamte Form. Keine neue Syntax: eine Funktionsdefinition und eine Zusicherung. Beides kennst du bereits.
Neu ist die Konvention. Der Name ist auch für etwas anderes als dich wichtig. Python Land sammelt aus deinen Testdateien Funktionen, deren Namen mit test_ beginnen, und führt jede einzeln aus. Ein Fehler im ersten Test verdeckt dadurch nicht die übrigen.
Tests liegen in eigenen Dateien, und auch deren Namen folgen einer Konvention. Eine Datei gilt als Testdatei, wenn sie tests.py heißt, mit test_ beginnt oder auf _test.py endet. Dieses Kapitel verwendet eine Datei pro Thema: test_question.py enthält die Tests für Question.
Run tests ist eine eigene Schaltfläche
Der Editor hat jetzt zwei Aktionen.
Run führt dein Programm so aus, wie eine Person es verwenden würde.
Run tests importiert deine Testmodule und deren Abhängigkeiten, führt dann jede test_-Funktion aus und meldet jedes Ergebnis. Die Anwendung wird dabei nicht über ihren geschützten Programmeinstieg gestartet. Ungeschützter Code auf Modulebene würde bei diesen Importen aber trotzdem laufen. Die Aktion schließt auch die Lektion nicht ab. Das macht weiterhin Submit, und Submit hat weiterhin eigene Prüfungen, die du nicht sehen kannst.
Run tests gehört also dir. Es zeigt dir, ob der Code das tut, was du glaubst, bevor jemand anderes danach fragt.
Das Verhalten testen, nicht die Umsetzung
Auf dieser Unterscheidung beruht der Rest des Kapitels.
Question gibt eine Zusage: Gib dem Objekt eine Antwort, und es sagt dir, ob sie richtig ist. Diese Zusage ist sein öffentliches Verhalten, von dem anderer Code abhängt.
Intern ruft is_correct derzeit .strip() und .lower() auf. Das ist die Umsetzung. Morgen könnte der Vergleich anders geschrieben werden. Solange die Zusage gilt, würde kein Nutzer von Question etwas davon bemerken.
Ein Test, der die Zusage prüft:
def test_capitalization_does_not_matter():
question = Question("Which keyword starts a function?", "def", 2)
assert question.is_correct("DEF")
Ein Test, der die Umsetzung prüft:
def test_is_correct_calls_lower():
...
Der erste Test übersteht eine Neufassung. Der zweite bricht bei einer Neufassung, die nichts Beobachtbares verändert hat. Ein Test, der fehlschlägt, obwohl nichts falsch ist, ist schlechter als kein Test: Du lernst, ihn zu ignorieren.
Kapitel 5 hat diese Grenze bereits zwischen den veröffentlichten Attributen einer Klasse und ihren internen Attributen gezogen. Bei Tests beginnt sich diese Grenze auszuzahlen.
Attempt hält einen laufenden Gesamtwert in self._earned und veröffentlicht ihn über die Property score. Welcher Test prüft öffentliches Verhalten?
Deinen ersten Test schreiben
Alles in diesem Kapitel baut auf einer bereits geschriebenen und funktionierenden Quizanwendung auf. Sie verwendet bewusst das frühere, schrittweise aufgebaute Quiz, das leer beginnt und über add() Fragen erhält. Seine Zustandsänderungen eignen sich gut zum Testen. Das Abschlussprojekt aus Kapitel 12 hatte eine andere Zusage: Sein ausführbares Quiz erhielt bei der Konstruktion eine vollständige Fragenliste und lehnte eine leere ab. Betrachte die Vorlage dieses Kapitels als verwandte Anwendung mit einer ausdrücklich anderen Grenze, nicht als dieselbe Klasse, die stillschweigend ihre Zusage ändert.
Deine Aufgabe in diesem Kapitel ist nicht, die Anwendung zu bauen. Du findest heraus, was sie tatsächlich zusagt, hältst diese Zusagen als Tests fest und nutzt sie dann, um den Code sicher zu ändern.
Beginne mit dem kleinsten Objekt darin.
Question erhält einen Fragetext, eine erwartete Antwort und eine Punktzahl. Seine einzige Methode, is_correct, vergleicht eine eingegebene Antwort mit der erwarteten und ignoriert dabei umgebende Leerzeichen sowie Groß- und Kleinschreibung.
Drei Zusagen verdienen es, festgehalten zu werden: Eine passende Antwort ist richtig, eine andere ist es nicht, und weder Leerzeichen noch Groß- und Kleinschreibung ändern das Urteil.
Aufgabe
Schreibe test_question.py, die erste Testdatei für die Quizanwendung.
Importiere Question aus quizapp.models und schreibe dann drei Testfunktionen:
test_a_matching_response_is_correct: Eine Antwort, die der erwarteten entspricht, ist richtig.test_a_different_response_is_not_correct: Eine andere Antwort ist es nicht.test_spacing_and_capitalization_do_not_matter: Eine Antwort wie" DEF "ist weiterhin richtig.
Jeder Test erstellt sein eigenes Question und prüft mit einer Zusicherung das Ergebnis von is_correct. Prüfe nur, was Question zusagt: Rufe im Test weder .strip() noch .lower() auf und greife auf kein Attribut zu, das mit einem Unterstrich beginnt. Kommentare, die diese Entscheidung erklären, sind in Ordnung.
Lass mit Run tests alle drei Tests bestehen, bevor du abgibst.