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()