https://docs.python.org/3/library/unittest.html
Moduł 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 katalogu 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 #unittest.main(argv=[''], exit=False) # w trybie interaktywnym (Jupyter Notebook) # 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