← Python/Testen
Les 4 van 8
MODULE 10 · 04◷ 30 min

Eén afspraak met meerdere gegevens testen

Na deze les kun je
  • Met pytest.mark.parametrize aparte testgevallen maken.
  • Invoer en verwachting als onafhankelijke waarden in een tabel zetten.
  • Verschillende testgevallen uit elkaar houden wanneer één geval faalt.

Je hebt vier bijna gelijke testfuncties geschreven: alleen de invoer en de verwachte uitkomst verschillen. Als je dezelfde vergelijking blijft kopiëren, moet je een verbetering op meerdere plaatsen doorvoeren. Een tabel met testgegevens maakt die herhaling overzichtelijker.

Een functie krijgt meerdere testgevallen

Parametrisatie laat pytest één testfunctie meerdere keren uitvoeren met verschillende gegevens. De decorator @pytest.mark.parametrize staat direct boven de functie. Het eerste argument noemt de parameters; het tweede bevat de rijen met hun waarden.

Bij "text,expected" krijgt iedere rij twee waarden. De eerste gaat naar text en de tweede naar expected. Gebruik diezelfde namen in de functiedefinitie, zodat pytest weet waar de waarden horen.

Het eerste voorbeeld controleert een eenvoudige formatter. Iedere rij heeft een eigen, vooraf berekende verwachting. De rapporteur laat zien dat één functie drie afzonderlijke pytest-gevallen oplevert.

Eén testfunctie krijgt drie aparte uitvoeringen
test_format.py
import pytest

@pytest.mark.parametrize("text,expected", [(" Sam ", "Sam"), ("", ""), ("  Mila", "Mila")])
def test_name(text, expected):
    assert text.strip() == expected
run_tests.py
import contextlib, io, pytest

class Recorded:
    def __init__(self):
        self.count = 0
        self.outcomes = []

    def pytest_collection_modifyitems(self, items):
        self.count = len(items)

    def pytest_runtest_logreport(self, report):
        if report.when == "call":
            self.outcomes.append(report.outcome)

recorded = Recorded()
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
    status = pytest.main(["-q", "--capture=sys", "-p", "no:cacheprovider", "test_format.py"], plugins=[recorded])
print("gevonden:", recorded.count)
print("geslaagd:", recorded.outcomes.count("passed"))
print("mislukt:", recorded.outcomes.count("failed"))
print("status:", int(status))
Verwachte uitvoer
gevonden: 3
geslaagd: 3
mislukt: 0
status: 0

De gegevens zijn onderdeel van de test

Een testgeval bestaat hier uit invoer plus verwachting. Kies beide vanuit de afspraak die je wilt controleren. Als je de verwachting met dezelfde functie berekent als de ontvangen waarde, kan dezelfde fout aan beide kanten ontstaan.

Schrijf daarom niet (4, rates.shipping(4)) in de tabel. Reken de uitkomst zelf na: vier eenheden van negen kosten zesendertig. De functie wordt pas binnen de test aangeroepen voor de echte uitkomst.

Een goed gekozen rij onderzoekt iets anders dan de vorige. Een lege hoeveelheid, precies één eenheid en meerdere eenheden kunnen verschillende fouten zichtbaar maken. Vier keer dezelfde invoer opschrijven levert vier uitvoeringen, maar weinig extra zekerheid.

Eén mislukte rij houdt de andere zichtbaar

Met een gewone lus in één test stopt de uitvoering bij de eerste mislukte assertie. Met parametrisatie verzamelt pytest afzonderlijke gevallen voordat het ze uitvoert. Een fout in één rij verhindert daardoor niet dat de andere rijen hun eigen resultaat krijgen.

Het tweede voorbeeld heeft bewust één verkeerde verwachting. Je ziet twee gevallen, waarvan er één slaagt en één mislukt. De fout zit hier in de testgegevens: controleer de afgesproken bewerking voordat je de productcode verandert.

De tweede rij faalt zonder de eerste te verbergen
test_format.py
import pytest

@pytest.mark.parametrize("text,expected", [(" sam ", "sam"), ("MILA", "Mila")])
def test_name(text, expected):
    assert text.strip() == expected
run_tests.py
import contextlib, io, pytest

class Recorded:
    def __init__(self):
        self.count = 0
        self.outcomes = []

    def pytest_collection_modifyitems(self, items):
        self.count = len(items)

    def pytest_runtest_logreport(self, report):
        if report.when == "call":
            self.outcomes.append(report.outcome)

recorded = Recorded()
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
    status = pytest.main(["-q", "--capture=sys", "-p", "no:cacheprovider", "test_format.py"], plugins=[recorded])
print("gevonden:", recorded.count)
print("geslaagd:", recorded.outcomes.count("passed"))
print("mislukt:", recorded.outcomes.count("failed"))
print("status:", int(status))
Verwachte uitvoer
gevonden: 2
geslaagd: 1
mislukt: 1
status: 1

Veelgemaakte fout: namen of aantallen vergeten

De namen in de decorator moeten overeenkomen met de functieparameters. Een rij met maar één waarde past niet bij twee genoemde namen. Pytest kan de test dan niet correct verzamelen; dat is een andere situatie dan een uitgevoerde vergelijking die onwaar is.

Een lege tabel is evenmin een nuttige controle. Bij de standaardinstellingen wordt het lege parametergeval overgeslagen. Kijk dus naar de werkelijk verzamelde gegevens en uitgevoerde controles, niet alleen naar de groene kleur van een eindstatus.

Eén verzendafspraak, vier gekozen situaties

De vaste module rates berekent negen per eenheid voor een niet-negatief geheel aantal. Maak één geparametriseerde functie test_shipping(count, expected) met de rijen (0, 0), (1, 9), (4, 36) en (7, 63). Vergelijk in die functie de echte uitkomst met expected.

De les controleert echte afzonderlijke pytest-gevallen en laat je test ook tegen gerichte rekenfouten lopen. Alleen vier waarden opsommen is dus onvoldoende: de test moet hun uitkomsten daadwerkelijk controleren.

AAN DE SLAG 0/4
1

Vier gevallen verzamelen

Vervang de lege tabel door de vier gevraagde paren en gebruik count en expected als functieparameters.

2

De lege hoeveelheid bewijzen

Laat je assertie bewijzen dat nul eenheden nul kost; een fout die minimaal één eenheid rekent moet worden gevonden.

3

Werkelijk met de invoer rekenen

Vergelijk rates.shipping(count) met expected, zodat een functie die altijd negen teruggeeft niet door de test komt.

4

Een groter aantal blijven controleren

Neem ook zeven eenheden met verwachting 63 op; je test moet een onterechte bovengrens van vijf ontdekken.

Onthouden

  • De decorator maakt één afzonderlijk testgeval per gegevensrij.
  • Parameternamen, functienamen en rijlengte moeten bij elkaar passen.
  • Kies onafhankelijke verwachtingen en verschillende betekenisvolle invoeren.

Hierna maak je gedeelde testvoorbereiding zonder veranderlijke toestand tussen tests te laten lekken.

test_rates.py
UitvoerPython
›
›_

Hier komt je resultaat.

Voer je code uit en ontdek
wat je hebt gemaakt.

0/4 stappen
EVEN MEEKIJKEN

Bekijk hoe het kan.

Je mag de oplossing bekijken. Deze poging telt dan niet mee als afgeronde les. Met Reset kun je daarna opnieuw zelf oefenen.