https://docs.python.org/3/library/unittest.html
Moduł unittest wspiera automatyzację testów, grupowanie testów w kolekcje, niezależność testów od narzędzi raportuących wyniki testów (reporting frameworks). Moduł dostarcza klasy, które ułatwiają te zadania. Ważne pojęcia:
Testy tworzymy w klasach wywiedzionych z unittest.TestCase. Jednostką testowania (test case) jest metoda bez parametrów, o nazwie rozpoczynającej się od "test", która ma sprawdzać jedną konkretną rzecz dotyczącą kodu. Test ma być przeprowadzony automatycznie, ma być niezależny od innych testów, a wynik ma być automatycznie zinterpretowany.
Testy mogą być grupowane w zestawach (test suits), typowy zestaw tworzą metody z jednej klasy wywiedzionej z unittest.TestCase.
Poniżej mamy przykład prostego modułu zawierającego funkcję average() do obliczania średniej arytmetycznej listy liczb. Kod testujący funkcję został umieszczony w tym samym module. Kod testujący zostanie wykonany, jeżeli moduł zostanie uruchomiony jako moduł główny.
W praktyce kod testujący moduł zwykle umieszcza się w osobnym pliku. Testy dla modułu spam.py umieszcza się w pliku test_spam.py. Często testy (pliki test_*.py) umieszcza się w osobnym podkatalogu tests.
import unittest
def average(values):
"""Oblicza średnią arytmetyczną listy liczb."""
return sum(values, 0.0) / len(values)
# testcase tworzymy przez dziedziczenie z unittest.TestCase.
# Kolejne testy to metody w klasie o nazwach zaczynających się
# od "test" (to jest konwencja dla test runnera).
# W każdej metodzie kluczowe jest zastosowanie funkcji:
# assertEqual(first, second, msg=None)
# - sprawdzanie wyniku (test first == second),
# msg to komunikat do wypisania przy wystąpieniu błędu;
# assertEqual(expression_to_test, expected_result) # typowa kolejność
# assertAlmostEqual(first, second, places=7, msg=None)
# assertAlmostEqual(first, second, places=7, msg=None, delta=None) [Py3.2+]
# - sprawdzanie wyniku typu float z domyślną dokładnością 7 cyfr;
# jeżeli podano delta, to sprawdzane jest |first - second| < delta
# assertNotEqual(first, second, msg=None)
# - sprawdzanie wyniku (test first != second);
# assertNotAlmostEqual(first, second, places=7, msg=None)
# assertNotAlmostEqual(first, second, places=7, msg=None, delta=None) [Py3.2+]
# - sprawdzanie wyniku;
# assertTrue(expr, msg=None)
# - sprawdzanie wartości logicznej (expr == True);
# assertFalse(expr, msg=None)
# - sprawdzanie wartości logicznej (expr == False);
# assertRaises(exception, callable, ...)
# - sprawdzenie wystąpienia spodziewanego wyjątku;
# assertRaises(exception, func, *args, **keywords)
# - sprawdzenie wyjątku func(*args, **keywords)
class TestStatisticalFunctions(unittest.TestCase):
# Czynności przygotowawcze dla test suit (cała klasa) [Py3.2+].
# Można utworzyć obiekt zbyt kosztowny, aby go tworzyć dla każdego test case.
@classmethod
def setUpClass(cls): pass
# Czynności przygotowawcze dla test case (metody test_*).
def setUp(self): pass
# Dla poprawnych danych wejściowych nie ma prawa pojawić się wyjątek.
def test_average_good(self):
# W testach nie używamy docstringów.
# Chcemy zachować standardowe komunikaty o błędach.
self.assertEqual(average([20, 30, 70]), 40.0)
self.assertEqual(round(average([1, 5, 7]), 1), 4.3)
# Nieprawidłowe dane powinny generować konkretny wyjątek.
def test_average_bad(self):
# Nie wstawiamy docstringu.
self.assertRaises(ZeroDivisionError, average, [])
self.assertRaises(TypeError, average, 20, 30, 70)
# Czynności czyszczące dla test case.
def tearDown(self): pass
# Czynności czyszczące dla test suit [Py3.2+].
@classmethod
def tearDownClass(cls): pass
# Załóżmy, że mamy w danym module zdefiniowane inne funkcje.
# Przygotowujemy nową klasę do testowania tych funkcji.
class TestOtherFunctions(unittest.TestCase):
def test_other(self):
pass
# Prosty sposób uruchomienia wszystkich testów z obu klas
# TestStatisticalFunctions i TestOtherFunctions.
if __name__ == '__main__':
unittest.main() # włącza wszystkie testy
#unittest.main(verbosity=2) # Python 2.7, więcej informacji
# W trybie interaktywnym lub w Jupyter Notebook:
#unittest.main(argv=['ignored'], exit=False)
# Jeżeli powyższy kod umieścimy w module 'average.py', to polecenie
# python3 average.py -v
# wyświetli szczegółowe informacje o testach.
# Zakładamy, że istnieje tylko klasa TestStatisticalFunctions.
# Uruchomienie testów, przy którym dostajemy więcej informacji.
# Mamy możliwość wyboru testów.
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(TestStatisticalFunctions)
unittest.TextTestRunner(verbosity=2).run(suite)
# Przy wielu klasach z testami możemy wybierać, z których klas
# mają pochodzić testy.
if __name__ == '__main__':
suite1 = unittest.TestLoader().loadTestsFromTestCase(TestStatisticalFunctions)
suite2 = unittest.TestLoader().loadTestsFromTestCase(TestOtherFunctions)
#suite = unittest.TestSuite([suite1, suite2]) # wszystkie testy
suite = unittest.TestSuite([suite2]) # wybrany zestaw testów
unittest.TextTestRunner(verbosity=2).run(suite)
# Możemy ręcznie wstawiać nazwy testów do zestawu.
if __name__ == '__main__':
suite = unittest.TestSuite() # pusty zestaw testów
suite.addTest(TestStatisticalFunctions("test_average_good"))
unittest.TextTestRunner(verbosity=2).run(suite)
# Przykład z książki Turnquista.
# Zakładamy, że istnieje tylko klasa TestStatisticalFunctions.
# Nazwy testów podajemy w wierszu poleceń:
# python skrypt.py test_name1 test_name2 ...
if __name__ == '__main__':
import sys
suite = unittest.TestSuite()
if len(sys.argv) == 1:
suite = unittest.TestLoader().loadTestsFromTestCase(
TestStatisticalFunctions)
else:
for test_name in sys.argv[1:]:
suite.addTest(TestStatisticalFunctions(test_name))
unittest.TextTestRunner(verbosity=2).run(suite)
Przykład tworzenia testów dla funkcji konwertujących liczby zapisane w systemie rzymskim można znaleźć w książce Marka Pilgrima Dive Into Python, dostępną za darmo pod adresem http://www.diveintopython.net/.
Moduł unittest dostarcza kilka dekoratorów używanych do pomijania testów (skip, skipIf, skipUnless, expectedFailure) oraz jedną metodę (TestCase.skipTest). Można pominąć pojedynczy test lub klasę z testami. Dla pominiętych testów nie działają setUp() i tearDown(). Dla pominiętych klas nie działają setUpClass() i tearDownClass().
class MyTestCase(unittest.TestCase):
@unittest.skip("demonstrating skipping")
def test_nothing(self):
self.fail("shouldn't happen")
@unittest.skipIf(mylib.__version__ < (1, 3),
"not supported in this library version")
def test_format(self):
# Tests that work for only a certain version of the library.
pass
@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_windows_support(self):
# windows specific testing code
pass
def test_maybe_skipped(self):
if not external_resource_available():
self.skipTest("external resource not available") # metoda
# test code that depends on the external resource
pass
@unittest.expectedFailure
def test_fail(self):
self.assertEqual(1, 0, "broken")
@unittest.skip("showing class skipping")
class MySkippedTestCase(unittest.TestCase):
def test_not_run(self): pass
Moduł unittest może być użyty w wierszu poleceń do uruchomienia testów z modułów, klas, czy metod testujących [Py3.2+].
$ python3 -m unittest test_module1 test_module2 $ python3 -m unittest test_module.TestClass $ python3 -m unittest test_module.TestClass.test_method
# Testowanie w trybie "Test Discovery" [Py3.2+]. $ python3 -m unittest discover