pyTest Tutorial #6: Parametryzacja testów

pyTest Tutorial #6: Parametryzacja testów

Poziom trudności
3.5/5

Parametryzacja testów w pyTestcie jest bardzo często używanym narzędziem, szczególnie gdy chcemy skorzystać z metodologii DDT (Data Driven Testing). Szczegóły takiego podejścia z wykorzystaniem Pythona oraz pyTest‚a zostało opisane w artykułach:

W tej części tutorialu skupimy się ogólnej zasadzie używania parametrów oraz sposobach parametryzowania testów. Standardowo wszystkie przykłady zostaną oparte na testowaniu klasy BasicCalculator, stworzonej w pierwszej części. 

Co to jest parametryzacja?

Najprościej rzecz ujmując, parametryzacja jest zestawem parametrów, które przekazujemy do testu, dzięki czemu jeden test może zostać uruchomiony n-razy z różnymi danymi. Podczas testowania, bardzo często zdarza się, że test z jednym zestawem danych nie jest wystarczający do przetestowania danej funkcjonalności. W takiej sytuacji parametryzacja jest strzałem w dziesiątkę. Najlepszym przykładem będą tutaj testy stworzone w poprzedniej części, które łatwo pogrupować względem ich zawartości na takie, które różnią się tylko danymi. Dzięki parametryzacji możemy w łatwy, i co ważne szybki sposób tworzyć wiele potrzebnych testów – bez zbędnego duplikowania kodu skryptów testowych.

Konstrukcja sparametryzowanych testów

Aby sparametryzować dany test, należy bezpośrednio przed nim umieścić marker parametrize, podać w nim odpowiednie parametry, z jakimi mają być uruchomione testy, a następnie przekazać je do testu w formie argumentów. Pamiętać należy także o tym, że w przypadku użycia jakiegokolwiek markera w pyTestcie, należy wcześniej dokonać importu modułu pytest (Więcej o markerach znajdziecie już w kolejnej części tego tutoriala)

  1. import pytest
  2. @pytest.mark.parametrize("value", [1, 2, 3])
  3. def test_sample(value):
  4. assert value < 3

Jak widać w powyższym przykładzie, test został sparametryzowany argumentem value, który przyjmuje wartości z listy podanej w markerze parametrize. Przy tworzeniu parametryzacji, należy pamiętać, że nazwy argumentów muszą być zgodne, zarówno te w markerze, jak i w definicji funkcji. Ponadto nazwy argumentów w markerze muszą być podane jako string, a jeśli potrzebujemy więcej niż jednego parametru, to kolejne oddzielamy przecinkiem w tym samym stringu. Przejdźmy więc do realnego przykładu testowania klasy BasicCalculator. Na wstępie przypomnijmy sobie testy z poprzedniej części, gdzie stworzyliśmy dla dodawania funkcjonalności skrypty testujące: 

  1. def test_addition_10_5()
  2. def test_addition_2_m4()
  3. def test_addition_m10_12()

W pierwszej kolejności należy się zastanowić jakie elementy musimy bądź chcemy sparametryzować. W powyższych testach będzie to pierwsza liczba, druga liczba oraz wynik. Docelowo nasz nowy test będzie wyglądał jak poniżej:

  1. @pytest.mark.parametrize("num1, num2, result", [(10, 5, 15),
  2. (2, -4, -2),
  3. (-10, 12, 2)])
  4. def test_addition(num1, num2, result):
  5. object = BasicCalculator()
  6. object.provide_number(num1)
  7. object.provide_operand('+')
  8. object.provide_number(num2)
  9. assert object.show_result()[0] == result

Umieśćmy w test_calc.py, pamiętając, aby jednocześnie dodać linię z importem biblioteki pytest. Przejdźmy teraz do uruchomienia nowego testu:

(env) qabrio@test:~/basic_calculator$ pytest test_calc.py::test_addition -v
============================================== test session starts ===============================================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /home/qabrio/basic_calculator/env/bin/python3.6
cachedir: .cache
rootdir: /home/qabrio/basic_calculator, inifile:
collected 3 items
test_calc.py::test_addition[10-5-15] PASSED [ 33%]
test_calc.py::test_addition[2--4--2] PASSED [ 66%]
test_calc.py::test_addition[-10-12-2] PASSED [100%]
============================================ 3 passed in 0.01 seconds ============================================
(env) qabrio@test:~/basic_calculator$

Jak widzimy na powyższym przykładzie, test_addition został uruchomiony trzy raz z różnymi, podanymi przez nas parametrami. Teraz, aby dopisać kolejny test funkcjonalności dodawania, wystarczy dopisać kolejny zestaw danych. Dobrą praktyką, jest umieszczenie listy z danymi jako osobny element, gdyż wpływa to korzystnie na czytelność kodu. Przykład widać poniżej: 

  1. addition_data = [(10, 5, 15),
  2. (2, -4, -2),
  3. (-10, 12, 2)
  4. ]
  5. @pytest.mark.parametrize("num1, num2, result", addition_data)
  6. def test_addition(num1, num2, result):
  7. object = BasicCalculator()
  8. object.provide_number(num1)
  9. object.provide_operand('+')
  10. object.provide_number(num2)
  11. assert object.show_result()[0] == result

Nie tylko ciekawostką, ale przydatną możliwością jest uruchomienie testów bez parametrów, a dokładnie z pustą listą jako parametry. Rezultatem takiego działania będzie pominięcie danego testu, czyli ustawienie rezultatu na skipped (w nawiasach przy pominiętym teście będą odwołania do zerowych elementów):

  1. addition_data = []
(env) qabrio@test:~/basic_calculator$ pytest test_calc.py::test_addition -v
============================================== test session starts ================================================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /home/qabrio/basic_calculator/env/bin/python3.6
cachedir: .cache
rootdir: /home/qabrio/basic_calculator, inifile:
collected 1 item
test_calc.py::test_addition[num10-num20-result0] SKIPPED [100%]
============================================ 1 skipped in 0.01 seconds ============================================
(env) qabrio@test:~/basic_calculator$

Generatory parametrów

Zestaw danych przekazywanych do parametrów nie musi być koniecznie liczbą. Z powodzeniem może to być na przykład funkcja, która zwraca nam jakieś wartości, czy generator, zarówno te opakowane w metody, jak i te wpisane bezpośrednio. Zobaczmy więc poniżej na przykład, zawierający prosty generator.

  1. @pytest.mark.parametrize("num1, num2, result", ((int(x/2), x - int(x/2), x) for x in range(1, 100, 12)))
  2. def test_addition(num1, num2, result):
  3. object = BasicCalculator()
  4. object.provide_number(num1)
  5. object.provide_operand('+')
  6. object.provide_number(num2)
  7. assert object.show_result()[0] == result

Generator dla parametrów przygotuje dane dla 9 testów, gdzie wyniki będą się mieściły w zakresie 0..100 ze skokiem 12, natomiast składniki dodawania będą takie same dla liczb parzystych oraz różniły się o jeden w przypadku liczb nieparzystych. Poniżej widzimy wynik wykonania testów:

(env) qabrio@test:~/basic_calculator$ pytest test_calc.py::test_addition -v
============================================== test session starts ===============================================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /home/qabrio/basic_calculator/env/bin/python3.6
cachedir: .cache
rootdir: /home/qabrio/basic_calculator, inifile:
collected 9 items
test_calc.py::test_addition[0-1-1] PASSED [ 11%]
test_calc.py::test_addition[6-7-13] PASSED [ 22%]
test_calc.py::test_addition[12-13-25] PASSED [ 33%]
test_calc.py::test_addition[18-19-37] PASSED [ 44%]
test_calc.py::test_addition[24-25-49] PASSED [ 55%]
test_calc.py::test_addition[30-31-61] PASSED [ 66%]
test_calc.py::test_addition[36-37-73] PASSED [ 77%]
test_calc.py::test_addition[42-43-85] PASSED [ 88%]
test_calc.py::test_addition[48-49-97] PASSED [100%]
============================================ 9 passed in 0.02 seconds ============================================
(env) qabrio@test:~/basic_calculator$

Nazwy zestawów parametrów

Pracując z parametrami testów, możemy dojść do sytuacji, gdzie nasze testy będą otrzymywały po 10 czy więcej parametrów. W takiej sytuacji czytelność takiego zapisu znacznie spada, co z kolei jest sporym utrudnieniem w analizowaniu wyników. Najlepszym sposobem, aby ułatwić sobie pracę ze złożoną parametryzacją jest wykorzystanie specjalnego argumentu parametryzacji – ids. Oznacza identyfikatory konkretnych zestawów parametrów. Aby je stworzyć, można wykorzystać generatory, które zrobią to za nas lub wykonujemy drugą listę z nazwami parametrów w poprzedniej. Osobiście jestem zwolennikiem wykorzystywania generatorów. Stwórzmy więc generator addition_names tworzący nazwy testów w bardziej czytelnej formie, korzystając z listy addition_data

  1. addition_data = [(10, 5, 15),
  2. (2, -4, -2),
  3. (-10, 12, 2)
  4. ]
  5. addition_names = (f"{x}_add_{y}_?_{r}" for x, y, r in addition_data)

Teraz dodajmy go do testu test_addition, a konkretnie do markera parametrize, w argumencie ids — tak jak jest to zrobione poniżej:

  1. @pytest.mark.parametrize("num1, num2, result", addition_data, ids=addition_names)
  2. def test_addition(num1, num2, result):
  3. object = BasicCalculator()
  4. object.provide_number(num1)
  5. object.provide_operand('+')
  6. object.provide_number(num2)
  7. assert object.show_result()[0] == result

W tym momencie po uruchomieniu zobaczymy wykonane testy, gdzie każdy zamiast parametrów zobaczymy nowe nazwy stworzone przez nas:

(env) qabrio@test:~/basic_calculator$ pytest test_calc.py::test_addition -v
=============================================== test session starts ===============================================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /home/qabrio/basic_calculator/env/bin/python3.6
cachedir: .cache
rootdir: /home/qabrio/basic_calculator, inifile:
collected 3 items
test_calc.py::test_addition[10_add_5_?_15] PASSED [ 33%]
test_calc.py::test_addition[2_add_-4_?_-2] PASSED [ 66%]
test_calc.py::test_addition[-10_add_12_?_2] PASSED [100%]
============================================ 3 passed in 0.01 seconds =============================================
(env) qabrio@test:~/basic_calculator$

Istnieje także alternatywny sposób tworzenie nazwanych parametrów, poprzez wykorzystanie wbudowanej funkcji pytest.param. Poniżej przykład modyfikacji parametrów, który da nam taki sam rezultat jak powyżej:

  1. addition_data = [pytest.param(10, 5, 15, id="10_add_5_?_15"),
  2. pytest.param(2, -4, -2, id="2_add_-4_?_-2"),
  3. pytest.param(-10, 12, 2, id="-10_add_12_?_2")
  4. ]
  5. @pytest.mark.parametrize("num1, num2, result", addition_data)
  6. def test_addition(num1, num2, result):
  7. object = BasicCalculator()
  8. object.provide_number(num1)
  9. object.provide_operand('+')
  10. object.provide_number(num2)
  11. assert object.show_result()[0] == result

Wielokrotna parametryzacja

Kolejną ciekawą możliwością jest wielokrotna parametryzacja, dzięki temu dany test może być sparametryzowany kilka razy. Parametryzacja odbywa się ‚od testu’, czyli najpierw pobierany jest zestaw parametrów najbliżej testu, następnie zestaw parametrów znajdujący się w drugim rzędzie, i tak dalej… Łatwiej będzie można to zobaczyć na przykładzie. Zmieńmy w takim razie test_addition, podmieniając rezultat na wynik działania dodawania:

  1. def test_addition(num1, num2):
  2. object = BasicCalculator()
  3. object.provide_number(num1)
  4. object.provide_operand('+')
  5. object.provide_number(num2)
  6. assert object.show_result()[0] == num1 + num2

Teraz możemy dodać parametry do powyższego testu:

  1. @pytest.mark.parametrize("num1", [1, 2, 3])
  2. @pytest.mark.parametrize("num2", [4, 5, 6])
  3. def test_addition(num1, num2):
  4. object = BasicCalculator()
  5. object.provide_number(num1)
  6. object.provide_operand('+')
  7. object.provide_number(num2)
  8. assert object.show_result()[0] == num1 + num2

Uruchomienie wyżej wykonanych testów pokaże nam rezultat z 9 testów:

(env) qabrio@test:~/basic_calculator$ pytest test_calc.py::test_addition -v
=============================================== test session starts ===============================================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /home/qabrio/basic_calculator/env/bin/python3.6
cachedir: .cache
rootdir: /home/qabrio/basic_calculator, inifile:
collected 9 items
test_calc.py::test_addition[4-1] PASSED [ 11%]
test_calc.py::test_addition[4-2] PASSED [ 22%]
test_calc.py::test_addition[4-3] PASSED [ 33%]
test_calc.py::test_addition[5-1] PASSED [ 44%]
test_calc.py::test_addition[5-2] PASSED [ 55%]
test_calc.py::test_addition[5-3] PASSED [ 66%]
test_calc.py::test_addition[6-1] PASSED [ 77%]
test_calc.py::test_addition[6-2] PASSED [ 88%]
test_calc.py::test_addition[6-3] PASSED [100%]
============================================ 9 passed in 0.02 seconds =============================================
(env) qabrio@test:~/basic_calculator$

W przypadku zamiany miejscami parametrów w powyższym przykładzie zmieni się kolejność testów, gdzie jako pierwszy zostałby wykonany test z parametrami 4-1, następnie 5-1, 6-1 itd. Analogicznie można dodawać kolejne poziomy parametrów.

Uruchamianie pojedynczego sparametryzowanego testu

Na zakończenie przyjrzyjmy się jeszcze uruchamianiu sparametryzowanych testów. Jak mogliście zauważyć, wybierając dany test, powodowaliśmy, że uruchamiały się całe zestawy testów. Istnieje jednak jeszcze sposób uruchomienia konkretnego testu z wybranymi parametrami. Wykonać to możemy za pomocą dodania zestawów parametrów podczas podawania testu:

(env) qabrio@test:~/basic_calculator$ pytest test_calc.py::test_addition[10_add_5_?_15] -v
=============================================== test session starts ===============================================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /home/qabrio/basic_calculator/env/bin/python3.6
cachedir: .cache
rootdir: /home/qabrio/basic_calculator, inifile:
collected 1 item
test_calc.py::test_addition[10_add_5_?_15] PASSED [100%]
============================================ 1 passed in 0.01 seconds =============================================
(env) qabrio@test:~/basic_calculator$

Analogicznie możemy uruchomić testy, które nie mają nazwanych parametrów:

(env) qabrio@test:~/basic_calculator$ pytest test_calc.py::test_addition[4-3] -v
=============================================== test session starts ===============================================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /home/qabrio/basic_calculator/env/bin/python3.6
cachedir: .cache
rootdir: /home/qabrio/basic_calculator, inifile:
collected 1 item
test_calc.py::test_addition[4-3] PASSED [100%]
============================================ 1 passed in 0.01 seconds =============================================
(env) qabrio@test:~/basic_calculator$

Należy pamiętać, że jeśli w parametrach mamy jakąkolwiek zmienną zawierającą spacje (np. string), musimy całość umieścić w apostrofach lub cudzysłowach:

(env) qabrio@test:~/basic_calculator$ pytest 'test_calc.py::test_addition[10 add 5 ? 15]' -v

Przebrnęliśmy przez kolejną część tutorial‚a związana z tworzeniem sparametryzowanych testów. Zachęcam do ćwiczeń we własnym zakresie, gdyż najlepiej utrwala się wiedzę poprzez praktykę. W kolejnej części zajmiemy się innymi markerami udostępnionymi przez pyTesta, a także spróbujemy stworzyć swoje!

pyTest Tutorial #6: Parametryzacja testów image

Dodaj komentarz