Testing Python
Python bietet einige Möglichkeiten um den Code zu testen.
doctest
Sucht in den Kommentaren nach interaktiven Python Sitzungen. Dadurch entstehen Kommentare welche die Funktionsweise erklären und gleichzeitig als Tests funktionieren.
def add_two_numbers(x, y):
'''Return the summary of x and y.
Examples:
>>> add_two_numbers(1, 5)
6
>>> add_two_numbers(20, -10)
10
>>> add_two_numbers(20, 0.23)
20.23
>>> add_two_numbers(0, 0)
Traceback (most recent call last):
...
ValueError: numbers must not be null
'''
if x == 0 or y == 0:
raise ValueError, 'numbers must not be null'
return x + y
if __name__ == '__main__':
import doctest
doctest.testmod()
Ruft man die Datei direkt auf werden die Tests ausgeführt, die Datei kann wie gewohnt importiert werden.
$ python add.py -v
$ python -m doctest -v add.py
unittest
Das Standard Unit Testing Framework für Python.
import add
import unittest
class TestCases(unittest.TestCase):
def test_simple_add(self):
self.assertEqual(6, add.add_two_numbers(1, 5), msg="simple add")
def test_negative_add(self):
self.assertEqual(10, add.add_two_numbers(20, -10))
def test_float_add(self):
self.assertEqual(20.23, add.add_two_numbers(20, 0.23))
def test_exception(self):
with self.assertRaises(ValueError):
add.add_two_numbers(0, 0)
if __name__ == '__main__':
unittest.main()
Auch hier das gleiche Verhalten beim direkten Aufruf werden die Tests ausgeführt. Der Test kann sich auch mit in der Datei befinden wie die Kommentare von doctest zuvor, hier ist es eine Datei die mit test_ beginnt.
$ python test_add.py -v
pytest
Ein weiteres Framework zum schreiben von Tests. Es muss installiert werden da es nicht zur Standard Bibliothek gehört.
$ pip install pytest
Pytest sucht im Verzeichnis nach Dateien die mit test_ beginnen und führt diese aus.
$ pytest -v
Auch doctest kann mit Pytest ausgeführt werden.
pytest --doctest-modules
Eine XML Datei erstellen oder nach dem ersten fehlgeschlagenen Test aufhören.
$ pytest --junitxml=report.xml
$ pytest -x
import add
import pytest
class TestClass:
def setup_method(self, method):
self.x = 'x'
def teardown_method(self, method):
del self.x
def test_simple_add(self):
assert 6 == add.add_two_numbers(1, 5)
def test_negative_add(self):
assert 10 == add.add_two_numbers(20, -10)
def test_float_add(self):
assert 20.23 == add.add_two_numbers(20, 0.23)
def test_exception(self):
with pytest.raises(ValueError):
add.add_two_numbers(0, 0)
Markers
pytest bietet die Möglichkeit Tests zu markieren. Eine Test kann mehr als einen Marker haben und ein Marker kann mehrere Tests markieren, Marker werden als Decorator geschrieben.
@pytest.mark.add
def test_simple_add(self):
assert 6 == add.add_two_numbers(1, 5)
@pytest.mark.float
def test_float_add(self):
assert 20.23 == add.add_two_numbers(20, 0.23)
Um nur bestimmte Tests auszuführen kann man mit -m und dem entsprechenden Ausdruck die Marker bestimmen.
pytest -v -m "add"
pytest -v -m "add and float"
Wenn nur ein Test ausgeführt werden soll kann dies beim Aufruf definiert werden.
pytest -v tests/test_add.py::test_simple_add
Alle verfügbaren Marker auflisten.
$ python -m pytest --markers
Der Builtin pytest.mark.parametrize Marker macht es möglich Parameter mitzugeben.
@pytest.mark.parametrize("x, y, z", [
(1, 1, 2),
(2, 1, 3),
(1, 2, 3)
])
def test_lists(x, y, z):
assert (x + y) == z
Der Marker kann auch mehrfach verwendet werden.
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_lists(x, y):
assert x < y
nose
War ein Fork von pytest als es die Version 0.8 hatte.
$ pip install nose
Nose sucht im Verzeichnis nach Dateien die mit test_* beginnen und führt diese aus.
$ nosetests
Auch Nose kann beim direkten Aufruf ausgeführt werden.
import add
import unittest
from nose.tools import assert_raises
class TestMain(unittest.TestCase):
def setUp(self):
self.obj = 'x'
def tearDown(self):
del self.obj
def test_simple_add(self):
assert 6 == add.add_two_numbers(1, 5)
def test_negative_add(self):
assert 10 == add.add_two_numbers(20, -10)
def test_float_add(self):
assert 20.23 == add.add_two_numbers(20, 0.23)
def test_exception(self):
with assert_raises(ValueError):
add.add_two_numbers(0, 0)
if __name__ == '__main__':
import nose
nose.run(defaultTest=__name__)