Hoofdstuk 11 - Code Testen
01 - Een functie testen
def get_formatted_name(first, last):
"""Maak een net opgemaakte naam"""
full_name = first + ' ' + last
return full_name.title()
- Om te controleren of bovenstaande functie ook werkt, kun je een programma maken dat deze functie gebruikt
def get_formatted_name(first, last):
"""Maak een net opgemaakte naam"""
full_name = first + ' ' + last
return full_name.title()
print("Enter 'q' om te stoppen")
while True:
first = input("\nType een voornaam: ")
if first == 'q':
break
last = input("Type een achternaam: ")
if last == 'q':
break
formatted_name = get_formatted_name(first, last)
print("\tDe opgegeven naam is : " + formatted_name + '.')
- Bovenstaande code verwerkt de namen goed
- Python heeft een efficiënte manier om het testen van de uitvoer van een functie te automatiseren
- De module unittest uit de standaard bibliotheek van Python biedt hulpmiddelen voor het testen van je code
- Een unit-test controleert of een specifiek aspect van het gedrag van je functie correct is
- Een test-case is een verzameling unit-tests die gezamenlijk aantonen dat een functie goed werkt
- Een goede test-case houdt rekening met alle soorten invoer die een functie kan ontvangen
- Een test-case met full coverage kan bij grote projecten intimiderend zijn
- Vaak is het voldoende om tests te schrijven voor het kritieke gedrag van je code
import unittest
def get_formatted_name(first, last):
"""Maak een net opgemaakte naam"""
full_name = first + ' ' + last
return full_name.title()
class NamesTestCase(unittest.TestCase):
"""Test voor de get_formatted_name()-functie"""
def test_first_last_name(self):
formatted_name = get_formatted_name('claes', 'compaen')
self.assertEqual(formatted_name, 'Claes Compaen')
unittest.main()
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
- Je kunt elke naam kiezen als naam voor je class, maar het beste verwerk je Test in de naam
- De class moet overerven van de class unittest.TestCase
- Elke methode die begint met test_ in de naamgeving zal automatisch worden uitgevoerd bij het aanroepen van de class
- Er zit één methode in de class die controleert of de naam correct wordt geretourneerd
- Je kunt in de test-methode de functie aanroepen die je wilt testen
- Stel je wilt de functie aanpassen met een tweede voornaam
def get_formatted_name(first, middle, last):
"""Maak een net opgemaakte naam"""
full_name = first + ' ' + middle + ' ' + last
return full_name.title()
- Deze versie werkt wel voor mensen met een tweede naam maar werkt niet meer voor mensen zonder tweede naam
- Als je nu de test erop zou uitvoeren krijgen we een melding dat de test niet slaagt
E
======================================================================
ERROR: test_first_last_name (__main__.NamesTestCase.test_first_last_name)
----------------------------------------------------------------------
Traceback (most recent call last):
File "<exec>", line 13, in test_first_last_name
TypeError: get_formatted_name() missing 1 required positional argument: 'last'
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
- De uitgebreide foutmelding laat precies zien welke unit test een error geeft
- Wanneer er veel unit tests worden uitgevoerd, is een uitgebreide foutmelding belangrijk
- Er wordt ook vermeld hoeveel unit tests er zijn uitgevoerd: Ran 1 test
- Repareer de fout door de tweede naam optioneel te maken
def get_formatted_name(first, last, middle=''):
"""Maak een net opgemaakte naam"""
if middle:
full_name = first + ' ' + middle + ' ' + last
else:
full_name = first + ' ' + last
return full_name.title()
- Na deze wijziging zal de test weer goed werken als iemand alleen één voornaam en achternaam opgeeft
- Maar deze nieuwe mogelijkheid moet op zijn beurt ook getest worden
import unittest
def get_formatted_name(first, last, middle=''):
"""Maak een net opgemaakte naam"""
if middle:
full_name = first + ' ' + middle + ' ' + last
else:
full_name = first + ' ' + last
return full_name.title()
class NamesTestCase(unittest.TestCase):
"""Test voor de get_formatted_name()-functie"""
def test_first_last_name(self):
"""Test één voornaam en achternaam"""
formatted_name = get_formatted_name('claes', 'compaen')
self.assertEqual(formatted_name, 'Claes Compaen')
def test_first_last_middle_name(self):
"""Test twee voornamen en achternaam"""
formatted_name = get_formatted_name('Jan', 'Reyning', 'Erasmus')
self.assertEqual(formatted_name, 'Jan Erasmus Reyning')
unittest.main()
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
- Het is logischer om de test class niet bij het programma te zetten maar in een aparte module
- De naam van methoden in de test class mogen best lang zijn, ze worden immers automatisch aangeroepen
02 - Een class testen
- Python biedt een aantal assert-methoden in de unittest.TestCase-class
- Assert-methoden testen of een conditie, waarvan jij denkt dat die waar moet zijn, ook daadwerkelijk waar is
| Methode |
Doel |
Voorbeeld |
assertEqual(a, b) |
Controleert of a == b |
self.assertEqual(2 + 2, 4) |
assertNotEqual(a, b) |
Controleert of a != b |
self.assertNotEqual(len("abc"), 5) |
assertTrue(x) |
Controleert of x waar is |
self.assertTrue(3 < 5) |
assertFalse(x) |
Controleert of x onwaar is |
self.assertFalse(10 in [1, 2, 3]) |
assertIn(a, b) |
Controleert of a in b zit |
self.assertIn("py", "python") |
assertNotIn(a, b) |
Controleert of a niet in b zit |
self.assertNotIn(4, [1, 2, 3]) |
assertGreater(a, b) |
Controleert of a > b |
self.assertGreater(10, 5) |
assertGreaterEqual(a, b) |
Controleert of a ≥ b |
self.assertGreaterEqual(5, 5) |
assertLess(a, b) |
Controleert of a < b |
self.assertLess(2, 10) |
assertLessEqual(a, b) |
Controleert of a ≤ b |
self.assertLessEqual(3, 3) |
assertAlmostEqual(a, b) |
Vergelijkt afrondingsverschil (handig bij floats) |
self.assertAlmostEqual(0.1 + 0.2, 0.3) |
assertRaises(Exception) |
Controleert of een fout wordt opgegooid |
with self.assertRaises(ValueError): int("abc") |
- Het testen van een class is vergelijkbaar met het testen van een functie
- In onderstaande code wordt de class getest met invoer van de gebruiker
class Rekenmachine:
"""Eenvoudige rekenmachine die optelt, vermenigvuldigt
en bijhoudt hoeveel berekeningen zijn uitgevoerd."""
def __init__(self):
self.aantal_bewerkingen = 0
def tel_op(self, x, y):
self.aantal_bewerkingen += 1
return x + y
def vermenigvuldig(self, x, y):
self.aantal_bewerkingen += 1
return x * y
def reset(self):
"""Zet de teller terug naar 0."""
self.aantal_bewerkingen = 0
# Gebruik van de class Rekenmachine met gebruikersinvoer
rekenmachine = Rekenmachine()
print("Welkom bij de rekenmachine!")
print("Kies: 1 = optellen, 2 = vermenigvuldigen, q = stoppen")
while True:
keuze = input("\nMaak een keuze: ")
if keuze == "q":
print("Programma beëindigd.")
break
if keuze not in ("1", "2"):
print("Ongeldige keuze. Kies 1, 2 of q.")
continue
try:
x = float(input("Eerste getal: "))
y = float(input("Tweede getal: "))
except ValueError:
print("Je moet wel een geldig getal invoeren!")
continue
if keuze == "1":
resultaat = rekenmachine.tel_op(x, y)
print("Uitkomst:", resultaat)
elif keuze == "2":
resultaat = rekenmachine.vermenigvuldig(x, y)
print("Uitkomst:", resultaat)
print("Aantal uitgevoerde bewerkingen:", rekenmachine.aantal_bewerkingen)
- De class kan ook getest worden met een unit-test
- Het is een goed idee om de unit-test in een andere module te zetten voor het overzicht
# rekenmachine.py
class Rekenmachine:
"""Eenvoudige rekenmachine die optelt, vermenigvuldigt
en bijhoudt hoeveel berekeningen zijn uitgevoerd."""
def __init__(self):
self.aantal_bewerkingen = 0
def tel_op(self, x, y):
self.aantal_bewerkingen += 1
return x + y
def vermenigvuldig(self, x, y):
self.aantal_bewerkingen += 1
return x * y
def reset(self):
"""Zet de teller terug naar 0."""
self.aantal_bewerkingen = 0
# test_rekenmachine.py
import unittest
from rekenmachine import Rekenmachine
class TestRekenmachine(unittest.TestCase):
"""Unittests voor de Rekenmachine-class."""
def setUp(self):
"""Wordt vóór elke test uitgevoerd."""
self.rm = Rekenmachine()
def test_tel_op_geeft_juiste_som(self):
resultaat = self.rm.tel_op(2, 3)
self.assertEqual(resultaat, 5)
resultaat = self.rm.tel_op(-1, 1)
self.assertEqual(resultaat, 0)
resultaat = self.rm.tel_op(2.5, 0.5)
self.assertAlmostEqual(resultaat, 3.0)
def test_vermenigvuldig_geeft_juiste_product(self):
resultaat = self.rm.vermenigvuldig(4, 5)
self.assertEqual(resultaat, 20)
resultaat = self.rm.vermenigvuldig(-2, 3)
self.assertEqual(resultaat, -6)
resultaat = self.rm.vermenigvuldig(2.5, 2)
self.assertAlmostEqual(resultaat, 5.0)
def test_aantal_bewerkingen_loopt_op(self):
# In het begin: 0
self.assertEqual(self.rm.aantal_bewerkingen, 0)
self.rm.tel_op(1, 2) # 1 bewerking
self.assertEqual(self.rm.aantal_bewerkingen, 1)
self.rm.vermenigvuldig(2, 3) # 2e bewerking
self.assertEqual(self.rm.aantal_bewerkingen, 2)
self.rm.tel_op(10, 10) # 3e bewerking
self.assertEqual(self.rm.aantal_bewerkingen, 3)
def test_reset_zet_teller_terug_naar_nul(self):
self.rm.tel_op(1, 1)
self.rm.vermenigvuldig(2, 2)
self.assertGreater(self.rm.aantal_bewerkingen, 0)
self.rm.reset()
self.assertEqual(self.rm.aantal_bewerkingen, 0)
if __name__ == "__main__":
unittest.main()
Oefening
Quiz
AI in de praktijk - Een e-mailfunctie testen
- In dit hoofdstuk heb je geleerd hoe je functies en classes kunt testen met
unittest
- In de praktijk is het belangrijk om te controleren of code blijft werken nadat je wijzigingen hebt gemaakt
- Ook AI-gegenereerde code kun je automatisch controleren met tests
- Stel dat je een functie hebt die een standaard ontvangstbevestiging maakt:
Beste Jan,
Wij hebben uw bericht ontvangen.
Wij nemen zo spoedig mogelijk contact met u op.
Met vriendelijke groet,
Training Minds
- Gebruik onderstaande prompt in een AI-tool
Je bent een Python-assistent voor beginners.
Maak een Python-programma dat een standaard ontvangstbevestiging maakt
en deze functie test met unittest.
Het programma moet:
- een functie bevatten met de naam make_email()
- een parameter gebruiken voor de naam van de ontvanger
- een standaard e-mailtekst teruggeven met return
- de naam van de ontvanger verwerken in de aanhef
Maak daarnaast een unittest die controleert:
- dat de naam van de ontvanger voorkomt in de e-mail
- dat de tekst "Wij hebben uw bericht ontvangen" voorkomt
- dat de tekst eindigt met "Training Minds"
Gebruik:
- functies
- parameters
- return
- strings
- unittest
- assertIn()
- assertTrue()
Gebruik geen:
- input()
- loops
- dictionaries
- classes behalve de unittest-class
- externe modules
- csv
- pandas
- json
Geef alleen de Python-code terug.
- Kopieer de prompt naar ChatGPT, Claude, Gemini of een andere AI-tool
- Kopieer de gegenereerde code naar de editor
- Voer de code uit
- Controleer of alle tests slagen
- Controleer of je de melding
OK krijgt
- Verander daarna zelf:
- één tekstregel in de e-mail
- de bedrijfsnaam
- de naam van de ontvanger in de test
Reflectievragen
- Welke functie wordt getest?
- Welke testmethode wordt automatisch uitgevoerd?
- Waarom begint de testmethode met
test_?
- Welke assert-methoden worden gebruikt?
- Wat controleert
assertIn()?
- Wat gebeurt er als je de e-mailtekst wijzigt?
- Waarom zijn tests handig bij wijzigingen in bestaande code?
- Kun je vertrouwen op AI-code zonder tests?
- Begrijp je elke regel van de testcode?
Extra uitdaging
- Breid de functie uit met een tweede parameter voor het onderwerp van de e-mail
- Laat AI ook een extra unittest maken voor deze nieuwe functionaliteit
Pas de functie aan zodat deze ook een parameter krijgt:
onderwerp
Verwerk het onderwerp in de e-mailtekst.
Maak daarnaast een extra unittest die controleert
of het onderwerp voorkomt in de e-mail.
Gebruik unittest.
Geef alleen de Python-code terug.
- Controleer of alle tests nog steeds slagen
- Voeg zelf nog een extra test toe