Top

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