0%

Objectgerichte programma’s testen en refactoren · oefening

Test meerdere gevallen

grade_letter is een trap:

def grade_letter(fraction):
    if fraction >= 0.9:
        return "A"

    if fraction >= 0.8:
        return "B"
    ...

Vijf vertakkingen en vier plekken waar het antwoord verandert. Eén test per geval schrijven geeft je vijf bijna identieke functies die alleen in twee waarden verschillen. Eén test voor het geheel schrijven riskeert niets nuttigs te zeggen als die faalt.

Er is een vorm die beide voordelen geeft.

Een lijst met gevallen, één lus

def test_each_band_gets_its_letter():
    cases = [
        (1.0, "A"),
        (0.9, "A"),
        (0.85, "B"),
        (0.6, "D"),
        (0.0, "F"),
    ]

    for fraction, expected in cases:
        assert grade_letter(fraction) == expected

De voorbereiding is de lijst. Elke rij is een geval, geschreven als invoer en wat die moet opleveren. Een geval toevoegen betekent een regel toevoegen.

Hoofdstuk 3 gebruikte precies deze vorm voor gegevens. Die leest hier hetzelfde, om dezelfde reden: als dingen alleen in hun waarden verschillen, maak de waarden dan de gegevens.

De fout die je niets vertelt

Voer die test uit tegen een defecte grade_letter en het rapport zegt:

test_each_band_gets_its_letter failed
assert grade_letter(fraction) == expected

Welke verhouding? Je kunt het niet zien. De lus draaide een onbekend aantal keren en stopte ergens.

Python-basis I noemde dat een assertie een bericht kan bevatten. In een lus is dat geen aardigheid meer:

    for fraction, expected in cases:
        assert grade_letter(fraction) == expected, (
            f"{fraction} should be {expected}, got {grade_letter(fraction)}"
        )

Nu noemt de fout het geval:

0.85 should be B, got C

De regel geldt algemeen: als één test meerdere gevallen controleert, moet het bericht zeggen welk geval. Anders heb je juist het voordeel van kleine tests opgegeven.

Een lustest met acht gevallen faalt. De assertie heeft geen bericht. Wat vertelt het rapport?

Kies gevallen bij de grenzen

Vijf vertakkingen hebben geen vijftig gevallen nodig. Ze hebben de waarden nodig waar het antwoord verandert en één aan elke kant.

0.9 is de grens tussen B en A. Zowel 0.9 als 0.89 verdient een rij, omdat >= en > één toetsaanslag verschillen en slechts één ervan juist is. Een geval bij 0.94 vertelt je bijna niets wat 0.9 niet al vertelde.

Dit is hetzelfde gevoel als het juiste antwoord én het onjuiste antwoord testen. Gevallen verdienen hun plek doordat ze zelfstandig kunnen falen.

Ook vastleggen: delen door niets

percentage heeft een vertakking die niets met letterbeoordelingen te maken heeft:

def percentage(score, total):
    if total == 0:
        return 0.0

    return score / total

Zonder die controle zou een lege quiz het rapport laten crashen met ZeroDivisionError. De controle is een beslissing die iemand heeft genomen. Met een test overleeft die beslissing de volgende persoon die de vertakking overbodig vindt.

Opdracht

Schrijf test_scoring.py voor quizapp.scoring.

Importeer grade_letter en percentage en schrijf daarna drie tests:

  • test_each_band_gets_its_letter: één lijst met gevallen (fraction, expected_letter), één lus en één assertie met een bericht dat het geval noemt. Dek elke letter af en neem een grens zelf en een waarde net eronder op (bijvoorbeeld 0.9 en 0.89).

  • test_percentage_of_an_empty_quiz_is_zero: percentage(0, 0) geeft 0.0 terug in plaats van een exceptie op te gooien.

  • test_percentage_divides_the_score_by_the_total: percentage(2, 5) geeft 0.4 terug.

De lustest heeft minstens zes gevallen nodig en het assertiebericht moet de verhouding bevatten die faalde.