Objektorientierte Programme testen und umstrukturieren · Abschlussprojekt
Abschlussprojekt: Projekt: Die Quizanwendung testen und umstrukturieren
Acht Lektionen mit Tests. Jetzt kommt der Grund dafür.
Refactoring bedeutet, die Anordnung des Codes zu verändern, ohne zu ändern, was er tut. Die Definition ist einfach. Praktisch möglich wird es erst, wenn du feststellen kannst, ob du die zweite Hälfte dieser Zusage eingehalten hast. Genau dafür sind deine Testdateien da.
Du hast gut zwanzig Tests für Question, Quiz, Attempt, die Bewertungsfunktionen, den Bericht und die beiden Ablehnungen. Zusammen beschreiben sie den Großteil dessen, was diese Anwendung zusagt. Das ist das Sicherheitsnetz. Jetzt darfst du dich bewusst hineinfallen lassen.
Das Problem noch einmal
Die letzte Lektion hat es gefunden. Die Notenzeile ist eine Idee über zwei Zahlen:
Ein Punktestand im Verhältnis zu einer Gesamtpunktzahl entspricht einem Buchstaben.
Erreichbar ist sie aber nur über einen Attempt, der ein Quiz voller Question-Objekte kennt. „4 von 5 ist ein B“ zu testen kostete sieben Zeilen Vorbereitung. Sechs davon dienten dazu, die Zahlen 4 und 5 an die richtige Stelle zu bringen.
Schau dir an, was report_lines mit seinem Argument macht:
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)}",
]
Es holt drei Werte aus dem Versuch und erwähnt ihn danach nicht mehr. Alles nach diesem ersten Auslesen ist das Bauen von Strings aus einem Namen und zwei Zahlen.
Der Schritt: Entscheiden vom Beschaffen trennen
Gib jeder Zeile eine Funktion, die das Benötigte entgegennimmt. Lass report_lines der Teil sein, der weiß, wo diese Werte liegen:
def grade_line(score, total):
return f"Grade: {grade_letter(percentage(score, total))}"
Jetzt beantwortet grade_line(4, 5) die Frage der letzten Lektion direkt in einer Zeile, ganz ohne Quiz. report_lines behält seine Aufgabe: zu wissen, dass ein Punktestand aus attempt.score stammt und eine Gesamtpunktzahl aus attempt.quiz.total_points, und die Zeilen in die richtige Reihenfolge zu bringen.
Das ist die Idee über Verantwortlichkeiten aus Kapitel 6, aus einer anderen Richtung erreicht. Dort war sie ein Argument zum Entwurf. Hier haben die Tests sie für dich gefunden.
Die Regeln für die Änderung
Ändere kein Verhalten. Weder den Wortlaut noch die Reihenfolge oder die Leerzeichen. report_lines muss exakt das zurückgeben, was es vorher zurückgegeben hat.
Führe die Tests während der ganzen Änderung aus. Nicht nur einmal am Ende, sondern nach jedem Schritt. So benennt ein Fehlschlag den Schritt, der ihn verursacht hat.
Lass die Tests unverändert. Das Sicherheitsnetz funktioniert nur, wenn es fest bleibt, während du dich bewegst. Einen Test an eine neue Ausgabe anzupassen macht aus einem erkannten Fehler eine Entscheidung, die du unbemerkt getroffen hast.
Die letzte Regel hat eine Ausnahme, die du heute nicht brauchst: wenn die Verhaltensänderung das Ziel ist. Dann änderst du zuerst bewusst den Test und beobachtest, wie er fehlschlägt, bevor du ihn zum Bestehen bringst.
Mitten im Refactoring schlägt test_an_empty_quiz_still_reports fehl. Was ist die richtige Reaktion?
Was du baust
report.py enthält am Ende vier Funktionen statt zwei.
headline(quiz) bleibt exakt unverändert.
score_line(learner, score, total) gibt die erste Zeile zurück: den Namen und dann die erzielten Punkte von der Gesamtpunktzahl.
grade_line(score, total) gibt die zweite zurück: das Wort Grade: und den Buchstaben. Den Anteil berechnet die Funktion dabei selbst.
report_lines(attempt) liest die drei Werte aus dem Versuch und gibt durch Aufruf der beiden anderen Funktionen die zwei Zeilen in der richtigen Reihenfolge zurück.
Am Ende sollte report_lines überhaupt keine f-Strings mehr enthalten. Daran erkennst du, dass die Formatierung ausgelagert wurde und nur das Beschaffen der Werte geblieben ist.
Aufgabe
Strukturiere report.py so um, dass sich die beiden Berichtszeilen ohne einen Attempt erstellen lassen.
Lass headline(quiz) exakt unverändert und füge zwei Funktionen hinzu:
score_line(learner, score, total)gibt für("Mina", 2, 5)den String"Mina: 2 of 5"zurück.grade_line(score, total)gibt für(2, 5)den String"Grade: F"zurück und berechnet den Anteil selbst.
Schreibe dann report_lines(attempt) so um, dass es learner, score und total_points aus dem Versuch liest und die beiden Zeilen durch Aufruf dieser Funktionen zurückgibt. Am Ende sollte es keine f-Strings enthalten.
Nichts an der Programmausgabe darf sich ändern. Führe während der Arbeit Tests aus: Alle deine Testdateien aus diesem Kapitel müssen weiterhin grün bleiben, und keine davon darf bearbeitet werden.