← Python/Testen
Les 7 van 8
MODULE 10 · 07◷ 35 min

Testgegevens kiezen rond groepen en grenzen

Na deze les kun je
  • Invoer verdelen in groepen met verschillend afgesproken gedrag.
  • Net vóór, op en na een grens testen.
  • Geldige buitenranden én afgewezen invoer controleren.

Een functie kan voor honderd gewone invoeren goed lijken en toch precies op een overgang fout zijn. Bij een verzendtarief is bijvoorbeeld belangrijk of duizend gram nog bij het eerste tarief hoort. Je kiest testgegevens daarom vanuit de afspraken, niet alleen vanuit makkelijke voorbeeldgetallen.

Groepen met hetzelfde soort gedrag

Een partitie is hier een groep invoeren waarvoor dezelfde gedragsregel geldt. Als een tarief tot en met duizend gram vier is, vormen die geldige gewichten samen één groep. Een zwaardere groep kan een ander tarief krijgen.

Kies eerst een duidelijke vertegenwoordiger uit iedere groep. Daarmee controleer je dat de hoofdregels bestaan. Eén voorbeeld per groep bewijst niet dat alle waarden goed zijn, maar helpt wel om een vergeten tak te ontdekken.

Neem de verwachting uit de beschreven afspraak. Dezelfde functie voor de ontvangen én verwachte waarde gebruiken laat haar fouten aan beide kanten terugkomen. Een test moet juist een onafhankelijk vergelijkingspunt hebben.

Een overgang van drie kanten bekijken

Een grensgeval ligt op of dicht bij de overgang tussen twee regels. Bij gehele getallen kun je het getal net vóór de grens, de grens zelf en het getal erna kiezen. Dat maakt het verschil tussen < en <= zichtbaar.

Het eerste voorbeeld gebruikt een fictief toegangskaartje. Vanaf twaalf kost het kaartje acht; daaronder nul. De rijen elf, twaalf en dertien leggen precies vast waar die overgang zit.

Net vóór, op en na een fictieve leeftijdsgrens
test_ticket.py
import pytest

def ticket(age):
    return 0 if age < 12 else 8

@pytest.mark.parametrize("age,expected", [(11, 0), (12, 8), (13, 8)])
def test_ticket(age, expected):
    assert ticket(age) == 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", "--color=no", "test_ticket.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

Het tweede voorbeeld verschuift die grens per ongeluk. De gewone leeftijden vijf en achttien blijven goed; de rij twaalf faalt. Meer willekeurige gewone waarden hadden deze specifieke fout niet noodzakelijk gevonden.

Twee gewone gevallen slagen; de grens onthult de verschuiving
test_shift.py
import pytest

def ticket(age):
    return 0 if age <= 12 else 8

@pytest.mark.parametrize("age,expected", [(5, 0), (18, 8), (12, 8)])
def test_ticket(age, expected):
    assert ticket(age) == 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", "--color=no", "test_shift.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: 2
mislukt: 1
status: 1

Ook de buitenkant hoort bij het contract

Een geldig bereik heeft soms een minimum en maximum. Test die uitersten als geldige invoer, en kies daarnaast waarden net daarbuiten die moeten worden afgewezen. Zo controleer je zowel accepteren als weigeren.

Een afgewezen waarde mag niet ongemerkt een gewoon resultaat krijgen. Gebruik daarvoor de foutverwachting uit de vorige les. Een verwachte fout en een gewone retourwaarde zijn verschillende afspraken.

Veelgemaakte fout: de grens aanpassen aan de code

Stel dat de beschrijving zegt tot en met duizend, maar de code gebruikt weight < 1000. Dan hoort de test met duizend te mislukken. De verwachting veranderen naar het hogere tarief zou de implementatiefout juist vastleggen als nieuwe regel.

Reken daarom eerst het contract na. Kijk vervolgens welke vergelijking of tak het gedrag veroorzaakt. Een mislukte grensrij geeft je een kleine plek om te onderzoeken.

Een volledig fictief verzendtarief

De module freight gebruikt gehele grammen van één tot en met drieduizend. Het tarief is vier tot en met duizend, zeven van duizend-één tot en met tweeduizend en negen daarboven tot en met drieduizend. Buiten het bereik geeft de functie ValueError met gewicht buiten bereik.

Schrijf geparametriseerde tests voor vertegenwoordigers, beide interne overgangen en de buitenranden. Gebruik per gevraagde waarde een afzonderlijk pytest-geval. Daarmee blijft zichtbaar welk onderdeel van de afspraak faalt.

De les probeert ook correcte alternatieve uitvoeringen. Je test moet hun afgesproken resultaten accepteren en gerichte verschuivingen vinden. Daarmee oefen je het contract, zonder één specifieke schrijfwijze van de productcode voor te schrijven.

AAN DE SLAG 0/4
1

Vertegenwoordigers uit iedere groep

Maak test_groups met aparte parametrisatiegevallen voor 500→4, 1500→7 en 2500→9. Controleer telkens freight.fee(weight).

2

De eerste overgang precies testen

Maak test_first_boundary met aparte gevallen 999→4, 1000→4 en 1001→7. Laat een onterechte < bij 1000 opvallen.

3

De tweede overgang precies testen

Maak test_second_boundary met aparte gevallen 1999→7, 2000→7 en 2001→9.

4

De geldige buitenranden afbakenen

Maak test_limits met aparte gevallen 1→4 en 3000→9. Maak test_outside met aparte gewichten 0 en 3001: verwacht ValueError met "gewicht buiten bereik".

Onthouden

  • Verdeel de invoer volgens verschillende gedragsregels en kies vertegenwoordigers.
  • Onderzoek een grens net ervoor, erop en erna.
  • Controleer geldige uitersten én de afwijzing net buiten het bereik.

Hierna maak je tests voorspelbaar wanneer code van tijd of uitvoer afhangt.

test_freight.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.