pyTest Tutorial #10: Setup & Teardown

pyTest Tutorial #10: Setup & Teardown

Poziom trudności
2.5/5

Niezależnie od tego, czy przy pisaniu testów zdecydowaliśmy się na grupowanie ich w klasy, czy pisaliśmy je bezpośrednio w modułach, mamy do dyspozycji dodatkowe narzędzia. Wśród nich jest narzędzie służące do wykonywania konfiguracji (setup) oraz czynności zamykających (teardown). Mogą one działać na różnych zakresach, takich jak zakres modułu, klasy czy funkcji/metody. Nic nie stoi na przeszkodzie, aby używać tylko wybranych lub wszystkich na raz.

Zasada działania

Niezależnie od tego, z którego będziemy planowali skorzystać, warto poznać je wszystkie. Narzędzia te są funkcjami, które muszą spełnić pewne warunki. Metody zawierające słowo setup, służą do przygotowywania konfiguracji przed testami, natomiast teardown wykonuje działania po zakończeniu testów w danym zakresie. Jako podstawę do dalszych przykładów przyjmijmy plik z testami jak poniżej.

  1. from base import BasicCalculator
  2. import pytest
  3. class TestAddSub:
  4. input_data = [(5, 10),
  5. (12, 7)
  6. ]
  7. @pytest.mark.parametrize("num1, num2", input_data)
  8. def test_addition(self, num1, num2):
  9. object = BasicCalculator()
  10. object.provide_number(num1)
  11. object.provide_operand('+')
  12. object.provide_number(num2)
  13. assert object.show_result()[0] == num1 + num2
  14. @pytest.mark.parametrize("num1, num2", input_data)
  15. def test_subtraction(self, num1, num2):
  16. object = BasicCalculator()
  17. object.provide_number(num1)
  18. object.provide_operand('-')
  19. object.provide_number(num2)
  20. assert object.show_result()[0] == num1 - num2

Setup i teardown - poziom klasy

W celu łatwego zobrazowania jak działa setup oraz teardown, zacznijmy od zakresu klasy. Od razu skorzystajmy z obu funkcji, czyli class_setup oraz class_teardown. W tym celu musimy dodać wewnątrz klasy, w dowolnym miejscu, dwie metody klasowe (opatrzone dekoratorem @classmethod, a także przekazujące argument cls). W celu lepszego zobrazowania, posłużę się poniższym przykładem:

  1. @classmethod
  2. def setup_class(cls):
  3. print('\n===Setup Class===')
  4. @classmethod
  5. def teardown_class(cls):
  6. print('\n===Teardown Class===')

Oczywiście na chwilę obecną te metody nie robią nic konkretnego, tylko wypiszą nam na ekranie informacje o tym, że się wykonały. Dzięki temu będziemy widzieć dokładnie, w którym miejscu zostały uruchomione:

qabrio@test:~/basic_calculator$ pytest -sv test_calc_8.py
============================================= test session starts ==============================================
platform linux -- Python 3.6.8, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/qabrio/basic_calculator
collected 4 items
test_calc_8.py::TestAddSub::test_addition[5-10]
===Setup Class===
5 + 10 = 15
PASSED
test_calc_8.py::TestAddSub::test_addition[12-7] 12 + 7 = 19
PASSED
test_calc_8.py::TestAddSub::test_subtraction[5-10] 5 - 10 = -5
PASSED
test_calc_8.py::TestAddSub::test_subtraction[12-7] 12 - 7 = 5
PASSED
===Teardown Class===
=========================================== 4 passed in 0.02 seconds ===========================================
qabrio@test:~/basic_calculator$

Jak widać, zgodnie z oczekiwaniami metoda setup_class została wykonana przed pierwszym testem, natomiast metoda teardown_class po ostatnim teście w klasie.

Setup i teardown - poziom modułu

Setup_module oraz teardown_module działa analogicznie do setup_class, oraz teardown_class, z zaznaczeniem, że działają na innych zakresach. W tym wypadku będzie to zakres modułu, czyli obręb pliku. Podstawowa składnia to seup_module(module) oraz teardown_module(module), z zaznaczenie, że parametr module jest opcjonalny. Przetestujmy zatem działanie tych metod w praktyce i dodajmy poniższe funkcje na początku pliku — po importach, lecz przed klasą. Oczywiście metody te można umieścić w dowolnym miejscu w pliku, lecz trzeba pamiętać, że nie mogą się znajdować wewnątrz klas czy jako podfunkcje. Ze względu na czytelność zaleca się umieszczanie ich na początku lub ewentualnie setup na początku pliku, a teardown an końcu.

  1. def setup_module():
  2. print('\n===Setup Module===')
  3. def teardown_module():
  4. print('\n===Teardown Module===')

Podobnie jak w przypadku klas, także i te metody wyświetlą tylko informacje o tym, że zostały uruchomione: 

qabrio@test:~/basic_calculator$ pytest -sv test_calc_8.py
============================================= test session starts ==============================================
platform linux -- Python 3.6.8, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/qabrio/basic_calculator
collected 4 items
test_calc_8.py::TestAddSub::test_addition[5-10]
===Setup Module===
===Setup Class===
5 + 10 = 15
PASSED
test_calc_8.py::TestAddSub::test_addition[12-7] 12 + 7 = 19
PASSED
test_calc_8.py::TestAddSub::test_subtraction[5-10] 5 - 10 = -5
PASSED
test_calc_8.py::TestAddSub::test_subtraction[12-7] 12 - 7 = 5
PASSED
===Teardown Class===
===Teardown Module===
=========================================== 4 passed in 0.02 seconds ===========================================
qabrio@test:~/basic_calculator$

Jak widać na przykładzie powyżej, zadziałały zarówno nowo dodane metody setup oraz teardown na poziomie modułu, jak i wcześniej dodane na poziomie klasy. Warto zauważyć, że pierwsze uruchomił się setup na poziomie modułu, a następnie setup związany z klasą. W przypadku teardown najpierw został uruchomiony teardown dla klasy, a potem dla modułu.

Setup i teardown - poziom metody w klasie

Znając już sposoby konfiguracji modułu oraz klasy, możemy przejść do konfiguracji pojedynczych metod wewnątrz klas. Od standardowych metod w klasach, różnią się nazwą, czyli setup_method(self, method) oraz teardown_method(self, method), ale także przyjmują opcjonalny parametr method — czyli nazwa metody, której dotyczy setup oraz teradown:

  1. def setup_method(self, method)
  2. def setup_method(self, method)

Spróbujmy zatem dodać w stworzonej wcześniej klasie funkcje setup oraz teardown dla pojedynczych testów:

  1. def setup_method(self):
  2. print('\===Setup Method===')
  3. def teardown_method(self):
  4. print('\n===Teardown Method===')

Teraz możemy uruchomić testy:

qabrio@test:~/basic_calculator$ pytest -sv test_calc_8.py
============================================= test session starts ==============================================
platform linux -- Python 3.6.8, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/qabrio/basic_calculator
collected 4 items
test_calc_8.py::TestAddSub::test_addition[5-10]
===Setup Module===
===Setup Class===
===Setup Method===
5 + 10 = 15
PASSED
===Teardown Method===
test_calc_8.py::TestAddSub::test_addition[12-7]
===Setup Method===
12 + 7 = 19
PASSED
<<Teardown Method<===
test_calc_8.py::TestAddSub::test_subtraction[5-10]
===Setup Method===
5 - 10 = -5
PASSED
===Teardown Method===
test_calc_8.py::TestAddSub::test_subtraction[12-7]
===Setup Method===
12 - 7 = 5
PASSED
===Teardown Method===
===Teardown Class===
===Teardown Module===
=========================================== 4 passed in 0.01 seconds ===========================================
qabrio@test:~/basic_calculator$

Setup i teardown na poziomie metod, zostały wykonane ‚najbliżej’ wybranej metody. Co ciekawe, w przypadku testów sparametryzowanych, setup oraz teardown zostanie wykonany przed i po każdej instancji sparametryzowanego testu.

Setup i teardown - poziom funkcji

Analogicznie do powyższego działa setup oraz teardown na poziomie funkcji. Jedyne różnice to, brak argumentu self, nie przekazujemy parametru method tylko function (także jest nieobowiązkowy) i oczywiście w nazwie używamy słowa function, a nie method:

  1. def setup_function(function)
  2. def teardown_function(function)

Najprościej można to zdefiniować, że różnica między poziomami method i function dotyczy testów wewnątrz klasy – method oraz bezpośrednio w module – function.

Warto zapamiętać!

Co istotne, jeśli na danym poziomie funkcja setup się nie powiedzie, odpowiednio funkcja teardown również nie zostanie wykonana. Na poziomach klas, metod, czy funkcji mamy do dyspozycji dodatkowy parametr, który po dodaniu możemy wykorzystać wewewnątrz funkcji lub metody. Jednym z przykładów może być wyświetlenie nazwy metody, dla której wykonał się teradown.

  1. def teardown_method(self, method):
  2. print('===Teardown Method===', method.__name__)

Co po uruchomieniu da nam:

PASSED('===Teardown Method===', 'test_subtraction')

Jak widzimy, są to bardzo przydatne funkcje, które mają bardzo duży potencjał podczas projektowania i wykonywania testów. Tak naprawdę poznane dzisiaj funkcje są wstępem do fixture, które poznamy w kolejnych odsłonach cyklu pyTest Tutorial.

pyTest Tutorial #10: Setup & Teardown image

Dodaj komentarz