0%

Objektorientierte Programme testen und umstrukturieren · Übung

Zusammenarbeitende Objekte testen

Bisher stand in jedem Test ein einzelnes Objekt im Mittelpunkt. report.py arbeitet anders:

def report_lines(attempt):
    fraction = percentage(attempt.score, attempt.quiz.total_points)
    return [
        f"{attempt.learner}: {attempt.score} of {attempt.quiz.total_points}",
        f"Grade: {grade_letter(fraction)}",
    ]

Ein Argument, durch das der Code auf Weiteres zugreift: einen Versuch, das zugehörige Quiz und zwei Funktionen aus scoring. Kapitel 6 hat diese Zusammenarbeit bewusst aufgebaut. Jetzt musst du sie testen.

Die ganze Situation vorbereiten

Der direkte Weg ist, die echten Objekte aufzubauen:

def test_the_report_names_the_learner_and_the_score():
    quiz = Quiz("Review")
    question = Question("Which keyword starts a function?", "def", 2)
    quiz.add(question)
    attempt = Attempt(quiz, "Mina")
    attempt.answer(question, "def")

    lines = report_lines(attempt)

    assert lines[0] == "Mina: 2 of 2"

Fünf Zeilen Vorbereitung für eine Zeile Handlung. Dieses Verhältnis ist bemerkenswert. Lektion 8 kommt darauf zurück, was es dir sagt. Vorerst ist es einfach der Preis dafür, eine Funktion zu testen, die mit einem kleinen Geflecht von Objekten arbeitet.

Der Vorteil ist real: Hier wird nichts vorgetäuscht. Wenn Attempt und Quiz nicht mehr zusammenpassen, findet dieser Test das heraus. Ein Test für nur eines der beiden würde es nicht merken.

Das Ergebnis prüfen, nicht die Partnerobjekte

report_lines gibt eine Liste von Strings zurück. Das ist seine gesamte Zusage, also prüfst du genau das.

Es ist verlockend, weiterzugreifen und zu prüfen, ob grade_letter aufgerufen wurde oder ob es mit 1.0 aufgerufen wurde. Widerstehe dem. Das sind Schritte, keine Ergebnisse. Eine Neufassung von report_lines, die den Buchstaben anders berechnet, aber dieselben Zeilen liefert, würde einen solchen Test brechen, ohne etwas tatsächlich Beobachtbares zu verändern.

Prüfe die Strings. Wenn die Strings stimmen, hat die Zusammenarbeit funktioniert.

Ein Stellvertreter, wenn die echten Objekte im Weg stehen

Manchmal möchtest du testen, wie eine Funktion mit einem Wert umgeht, der sich mit den echten Objekten nur umständlich erzeugen lässt. Kapitel 9 zeigte, dass Python darauf achtet, was ein Objekt tut, nicht aus welcher Klasse es stammt. Das gilt auch hier:

class FakeQuiz:
    def __init__(self, total_points):
        self.total_points = total_points


class FakeAttempt:
    def __init__(self, learner, score, total_points):
        self.learner = learner
        self.score = score
        self.quiz = FakeQuiz(total_points)

report_lines fragt nie, welche Klasse ihm übergeben wurde. Es benötigt .learner, .score und .quiz.total_points. Ein Stellvertreter mit diesen Attributen funktioniert bestens.

Das ermöglicht Genauigkeit. Du möchtest den Bericht für jemanden mit 4 von 5 Punkten? Gib es direkt an, statt ein Quiz mit passend verteilten Punkten zu bauen und genügend Fragen zu beantworten.

Es hat auch einen Preis: Ein Stellvertreter ist eine Behauptung darüber, wie sich das echte Objekt verhält. Ändert sich das echte Objekt, kann er problemlos weiter etwas Falsches behaupten. Verwende standardmäßig echte Objekte. Greife zu einem Stellvertreter, wenn deren Vorbereitung zu verdecken beginnt, worum es im Test geht.

report_lines erhält einen FakeAttempt mit learner, score und quiz.total_points. Warum funktioniert das?

Was der Bericht zusagt

headline(quiz) gibt einen String zurück: den Titel und danach die Gesamtpunktzahl in Klammern.

report_lines(attempt) gibt zwei zurück: den Namen des Lernenden mit den erzielten Punkten von der Gesamtpunktzahl sowie eine Notenzeile. Für jemanden mit 2 von 5 Punkten sind das "Mina: 2 of 5" und "Grade: F".

Das sind die Zusagen. Halte sie fest.

Aufgabe

Schreibe test_report.py und decke ab, was report.py erzeugt.

Importiere headline und report_lines aus report sowie alles Benötigte aus quizapp.models. Schreibe drei Tests:

  • test_the_headline_names_the_quiz_and_its_points: Für ein Quiz mit dem Titel „Chapter 13 Review“ und 5 möglichen Punkten gibt headline den String "Chapter 13 Review (5 points)" zurück.

  • test_the_report_names_the_learner_and_the_score: Baue ein echtes Quiz und einen Versuch, bei dem Mina 2 von 5 Punkten erreicht. Prüfe, dass die erste Zeile "Mina: 2 of 5" ist.

  • test_the_grade_line_reports_the_letter: Prüfe, dass die zweite Zeile desselben Berichts "Grade: F" ist.

Verwende echte Quiz-, Question- und Attempt-Objekte. Prüfe die zurückgegebenen Strings, nicht, welche Bewertungsfunktionen aufgerufen wurden.