0%

Objectgerichte programma’s testen en refactoren · oefening

Test samenwerkende objecten

De tests tot nu toe bevatten elk één object. report.py werkt 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)}",
    ]

Eén argument, en daarachter: een poging, de quiz waarbij die poging hoort en twee functies uit scoring. Hoofdstuk 6 bouwde deze samenwerkingen bewust. Nu moet je ze testen.

Bereid de hele situatie voor

De eenvoudige manier is de echte objecten bouwen:

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"

Vijf regels voorbereiding voor één regel uitvoering. Die verhouding is het opmerken waard en les 8 komt terug op wat die vertelt. Voor nu is het simpelweg de prijs van het testen van een functie die met een klein netwerk van objecten werkt.

Het voordeel is echt: niets doet hier alsof. Als Attempt en Quiz niet meer op elkaar aansluiten, ontdekt deze test dat. Een test van één van beide afzonderlijk zou dat niet doen.

Controleer het resultaat, niet de samenwerkingspartners

report_lines geeft een lijst met strings terug. Dat is de hele belofte en dus wat je controleert.

Het is verleidelijk verder te gaan en te controleren of grade_letter is aangeroepen of met 1.0 is aangeroepen. Weersta dat. Het zijn stappen, geen resultaten. report_lines herschrijven zodat die de letter anders berekent maar dezelfde regels oplevert, zou zo’n test stukmaken zonder iets werkelijks stuk te maken.

Controleer de strings. Als de strings kloppen, werkte de samenwerking.

Een vervanger als het echte object in de weg zit

Soms wil je testen hoe een functie een waarde afhandelt die met de echte objecten lastig te maken is. Hoofdstuk 9 liet zien dat Python geeft om wat een object doet, niet om de klasse waaruit het komt. Dat geldt hier ook:

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 vraagt nooit welke klasse die kreeg. De functie vraagt om .learner, .score en .quiz.total_points, en een vervanger die die heeft werkt perfect.

Dat geeft je precisie. Wil je het rapport voor een cursist die 4 van de 5 punten haalde? Zeg dat rechtstreeks, in plaats van een quiz te bouwen waarvan de vragen toevallig zo optellen en er genoeg te beantwoorden.

Het kost je ook iets: een vervanger is een bewering over hoe het echte object zich gedraagt en blijft zonder bezwaar verkeerd nadat het echte object verandert. Gebruik standaard echte objecten. Gebruik een vervanger wanneer het voorbereiden van de echte objecten begint te verbergen waarover de test gaat.

report_lines krijgt een FakeAttempt met learner, score en quiz.total_points. Waarom werkt dat?

Wat het rapport belooft

headline(quiz) geeft één string terug: de titel en daarna het puntentotaal tussen haakjes.

report_lines(attempt) geeft er twee terug: de cursist met de score ten opzichte van het totaal en een regel met een letterbeoordeling. Voor een cursist die 2 van de 5 punten haalde is dat "Mina: 2 of 5" en "Grade: F".

Dat zijn de beloften. Leg ze vast.

Opdracht

Schrijf test_report.py voor wat report.py oplevert.

Importeer headline en report_lines uit report en wat je nodig hebt uit quizapp.models. Schrijf drie tests:

  • test_the_headline_names_the_quiz_and_its_points: voor een quiz met titel “Chapter 13 Review” van 5 punten geeft headline "Chapter 13 Review (5 points)" terug.

  • test_the_report_names_the_learner_and_the_score: bouw een echte quiz en poging waarin Mina 2 van de 5 punten scoort en controleer dat de eerste regel "Mina: 2 of 5" is.

  • test_the_grade_line_reports_the_letter: controleer dat de tweede regel van hetzelfde rapport "Grade: F" is.

Gebruik echte Quiz-, Question- en Attempt-objecten. Controleer de teruggegeven strings, niet welke scorefuncties werden aangeroepen.