Unit-Tests#
Funktionstest#
Unit-Tests sind zum Vermeiden und Diagnostizieren von Fehlern sehr wichtig. Ein Unit-Test testet ein einzelnes Code-Modul, wie z.B. eine Funktion oder eine Klasse.
Zum Testen verwendet man in Python meist spezielle Pakete wie unittest
oder nosetest
, welche dann automatisch bei Code-Änderungen ausgeführt werden. Einfache Tests können direkt in den Code geschrieben werden. Hierfür ist der assert
Befehl da. Er prüft, ob eine Testbedingung erfüllt ist (assert) und löst eine AssertionError
Exception aus, wenn dies nicht der Fall ist.
Wir wollen zum Beispiel die zuvor definierte Divisionsfunktion division()
testen.
def division(zaehler, nenner):
if not isinstance(nenner, (int, float)):
raise ValueError(f"Nenner nicht vom Datentyp `int` oder `float`")
elif not isinstance(zaehler, (int, float)):
raise ValueError(f"Zaehler nicht vom Datentyp `int` oder `float`")
elif not nenner:
print("Warnung Division durch 0")
return None
else:
ergebnis = zaehler / nenner
print(f"Das Ergebnis von {zaehler}/{nenner} = {ergebnis}")
return ergebnis
Hierfür schreiben wir als erstes einen Funktionstest. Diese überprüfen die korrekte Funktion eines Moduls mit mehreren Eingaben, für die wir das korrekte Ergebnis kennen. Hier macht man meist mehrere Tests, um sicherzustellen, dass man nicht zufällig
das richtige Ergebnis bekommt, sondern für verschiedene Kombinationen.
def test_funktion(): # Die übliche Notation für Tests in Python ist die Funktionsnamen mit `test_` zu beginnen
assert division(10, 2) == 5
assert division(10, 5) == 2
assert division(50, 1) == 50
assert division(50, 5) == 10
test_funktion()
Das Ergebnis von 10/2 = 5.0
Das Ergebnis von 10/5 = 2.0
Das Ergebnis von 50/1 = 50.0
Das Ergebnis von 50/5 = 10.0
Grenzwerttest#
Ein weiterer wichtiger Test ist der Grenzwerttest. Hier prüft man ob Eingaben im Grenzwertbereich korrekt behandelt werden. Zum Beispiel ob positiv oder negativ Unendlich behandelt werden oder ob die Division durch 0 behandelt wird. Wir haben oben beim Erstellen der Funktion definiert, dass bei einer Division durch 0 die Funktion den Wert None
ausgeben soll.
import math # die Zahl unendlich ist in der math bibliothek definiert
def test_grenzwert():
assert division(math.inf, 1) == math.inf
assert division(math.inf, 100) == math.inf
assert math.isnan(division(math.inf, math.inf)) # Das prüfen auf nan (not a number) ist nur durch die funktion `isnan` moeglich
assert division(1, math.inf) == 0
assert division(100, 0) is None
test_grenzwert()
Das Ergebnis von inf/1 = inf
Das Ergebnis von inf/100 = inf
Das Ergebnis von inf/inf = nan
Das Ergebnis von 1/inf = 0.0
Warnung Division durch 0
Datentyptest#
Da Python dynamische Datentypen unterstützt ist es sehr wichtig Datentyptest. Hierbei wird getetstet ob die Funktion falsche Datentypen als Eingabe richtig behandelt.
Hierbei können wir nicht direkt assert
nutzen da es ja unseren Code zum Absturz bringt.
assert division("falscher typ", 1) is ValueError
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[6], line 1
----> 1 assert division("falscher typ", 1) is ValueError
Cell In[1], line 5, in division(zaehler, nenner)
3 raise ValueError(f"Nenner nicht vom Datentyp `int` oder `float`")
4 elif not isinstance(zaehler, (int, float)):
----> 5 raise ValueError(f"Zaehler nicht vom Datentyp `int` oder `float`")
6 elif not nenner:
7 print("Warnung Division durch 0")
ValueError: Zaehler nicht vom Datentyp `int` oder `float`
Stattdessen nutzen wir im Test einen try-except
-Block und die else
-Anweisung. Sie wird nur dann ausgeführt, wenn im try
-Block keine Exception auftritt, was im Testfall ja unerwünscht ist, also raise
wir in diesem Fall ein AssertionError, da der Testfall scheitert. Ferner prüfen wir, ob die geworfene Exception der Funktion auch den richtigen Typen TypeError
hat.
def test_datentyp():
# wir testen den Zähler
try:
division("falscher typ", 1)
except Exception as e:
assert isinstance(e, ValueError)
else:
raise AssertionError("Datatype")
# den zweiten Fall des Nenners
try:
division(10, "falscher typ")
except Exception as e:
assert isinstance(e, ValueError)
else:
raise AssertionError("Datatype")
test_datentyp()
Mit Tests bei Code-Änderungen Fehler erkennen#
Diese Unit-Tests haben wir absichtlich als Funktion definiert, so dass wir sie nun bei Code-Änderungen immer wieder durchführen können. So kann man dann testen, ob die Implementation die gestellten Erwartungen überhaupt oder immer noch erfüllt.
Wird zum Beispiel die Divisionsfunktion durch eine triviale Implementation überschrieben.
def division(zaehler, nenner):
return zaehler / nenner
So funktioniert immer noch unser Funktionstest. Weshalb es meistens nicht hinreichend ist, nur diesen zu implementieren oder bei der Entwicklung nur die Funktion zu prüfen.
test_funktion()
Der Grenzwerttest wird allerdings jetzt eine Ausnahme werfen.
test_grenzwert()
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Cell In[11], line 1
----> 1 test_grenzwert()
Cell In[4], line 8, in test_grenzwert()
6 assert math.isnan(division(math.inf, math.inf)) # Das prüfen auf nan (not a number) ist nur durch die funktion `isnan` moeglich
7 assert division(1, math.inf) == 0
----> 8 assert division(100, 0) is None
Cell In[9], line 2, in division(zaehler, nenner)
1 def division(zaehler, nenner):
----> 2 return zaehler / nenner
ZeroDivisionError: division by zero
Auch der Datentyptest ist nicht erfolgreich, weil wir ja definiert haben, dass bei falschen Eingabedatentypen ein ValueError
erzeugt werden soll, wir aber einen TypeError
erhalten.
test_datentyp()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[7], line 4, in test_datentyp()
3 try:
----> 4 division("falscher typ", 1)
5 except Exception as e:
Cell In[9], line 2, in division(zaehler, nenner)
1 def division(zaehler, nenner):
----> 2 return zaehler / nenner
TypeError: unsupported operand type(s) for /: 'str' and 'int'
During handling of the above exception, another exception occurred:
AssertionError Traceback (most recent call last)
Cell In[12], line 1
----> 1 test_datentyp()
Cell In[7], line 6, in test_datentyp()
4 division("falscher typ", 1)
5 except Exception as e:
----> 6 assert isinstance(e, ValueError)
7 else:
8 raise AssertionError("Datatype")
AssertionError: