pyTest Tutorial #7: Markery wbudowane i własne

pyTest Tutorial #7: Markery - wbudowane

Poziom trudności
3/5

W poprzedniej części skorzystaliśmy z wbudowanego w pyTest’a markera parametrize, dzięki któremu w łatwy sposób można tworzyć wiele testów z różnymi danymi testy. PyTest jednak oferuje nam więcej markerów, wbudowane takie jak skip, skipif czy xfail. Używanie ich może okazać się dla nas pomocne. Dodatkowo możemy tworzyć własne markery, o czym również napiszę w tej części.

Marker SKIP

Zacznijmy od prostego markera skip. Umieszczenie go przed testem sprawia, że test ten podczas uruchamiania jest pomijany, czyli ma status SKIPPED. Używamy go w przypadku gdy z jakiegoś powód nie chcemy go wykonywać, czyli gdy na przykład jest w trakcie tworzenia. Jeśli dany test jest sparametryzowany, spowoduje to, że nie wykona się dla wszystkich parametrów.

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

Po uruchomieniu testu zobaczymy:

(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] SKIPPED [ 33%]
test_calc.py::test_addition[2--4] SKIPPED [ 66%]
test_calc.py::test_addition[-10-12] SKIPPED [100%]
============================================ 3 skipped in 0.01 seconds ============================================
(env) qabrio@test:~/basic_calculator$

Użyteczną możliwością jest podawanie powodu, dla którego dany test ma być pominięty, Uczynić to możemy poprzez wypełnienie argumentu reason.

  1. @pytest.mark.skip(reason="Test is not ready to run")

Jeśli spróbujemy uruchomić taki test nic się nie powinno zmienić. Profity z takiego rozwiązania możemy zobaczyć dopiero po dodaniu parametru -r z opcją s (od skipped). Pojawi się wtedy dodatkowa sekcja short test summary info, w której będzie podany powód uruchomienia testu.

(env) qabrio@test:~/basic_calculator$ pytest -vr s test_calc.py::test_addition
================================================ 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] SKIPPED [ 33%]
test_calc.py::test_addition[2--4] SKIPPED [ 66%]
test_calc.py::test_addition[-10-12] SKIPPED [100%]
============================================== short test summary info ==============================================
SKIP [3] test_calc.py:24: Test is not ready to run
============================================= 3 skipped in 0.01 seconds =============================================
(env) qabrio@test:~/basic_calculator$

Marker SKIPIF

Kolejnym przydatnym markerem jest skipif. Działa analogicznie do skip, ale jest uzależniony od spełnienia warunku podanego w nawiasie. Jako warunek może być zmienna lub wyrażenie, które zwróci nam wartość True lub False. Poniżej przykład, gdzie uruchamianie testów jest uzależnione od zmiennej lock_addition:

  1. lock_addition = True
  2. @pytest.mark.skipif(condition=lock_addition, reason="Test is locked")
  3. @pytest.mark.parametrize("num1, num2", addition_data)
  4. def test_addition(num1, num2):
  5. object = BasicCalculator()
  6. object.provide_number(num1)
  7. object.provide_operand('+')
  8. object.provide_number(num2)
  9. assert object.show_result()[0] == num1 + num2

Należy tutaj pamiętać, że testy są skippowane wtedy, gdy rezultat warunku jest prawdziwy. Spróbujmy zatem uruchomić:

(env) qabrio@test:~/basic_calculator$ pytest -vr s test_calc.py::test_addition
================================================ 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] SKIPPED [ 33%]
test_calc.py::test_addition[2--4] SKIPPED [ 66%]
test_calc.py::test_addition[-10-12] SKIPPED [100%]
============================================== short test summary info ==============================================
SKIP [3] test_calc.py:25: Test is locked
============================================= 3 skipped in 0.01 seconds =============================================
(env) qabrio@test:~/basic_calculator$

Marker XFAIL

Trzecim markerem, równie ważnym co poprzednie, jest xfail. Marker ten służy do oznaczania testów, w przypadku których spodziewamy się, że zakończą się niepowodzeniem. Aby z niego skorzystać, podobnie do powyższych, trzeba przed testem umieścić linijkę:

  1. @pytest.mark.xfail

Po uruchomieniu takiego testu zobaczymy:

(env) qabrio@test:~/basic_calculator$ pytest -v test_calc.py::test_addition
================================================ 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] xfail [ 33%]
test_calc.py::test_addition[2--4] xfail [ 66%]
test_calc.py::test_addition[-10-12] xfail [100%]
============================================= 3 xfailed in 0.02 seconds =============================================
(env) qabrio@test:~/basic_calculator$

W celu uzyskania szczegółów możemy podobnie jak w powyższym skorzystać z parametru r z wartością x:

(env) qabrio@test:~/basic_calculator$ pytest -vr x test_calc.py::test_addition
================================================ 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] xfail [ 33%]
test_calc.py::test_addition[2--4] xfail [ 66%]
test_calc.py::test_addition[-10-12] xfail [100%]
============================================== short test summary info ==============================================
XFAIL test_calc.py::test_addition[10-5]
XFAIL test_calc.py::test_addition[2--4]
XFAIL test_calc.py::test_addition[-10-12]
============================================= 3 xfailed in 0.02 seconds =============================================
(env) qabrio@test:~/basic_calculator$

Podczas używania tego markera, ważne jest pamiętać, że jeśli otrzymamy rezultat pozytywny, podczas gdy spodziewaliśmy się negatywnego, test taki zostanie oznaczony jak XPASS. Aby go zobaczyć w części podsumowującej, należy do parametru -r dodać X:

(env) qabrio@test:~/basic_calculator$ pytest -vr xX test_calc.py::test_addition
================================================ 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] XPASS [ 33%]
test_calc.py::test_addition[2--4] XPASS [ 66%]
test_calc.py::test_addition[-10-12] XPASS [100%]
============================================== short test summary info ==============================================
XPASS test_calc.py::test_addition[10-5]
XPASS test_calc.py::test_addition[2--4]
XPASS test_calc.py::test_addition[-10-12]
============================================= 3 xpassed in 0.01 seconds =============================================
(env) qabrio@test:~/basic_calculator$

Marker xfail może przyjmować kilka parametrów, takich jak:
run — definiuje czy test ma zostać uruchomiony (True) czy nie (False). W przypadku ustawienia flagi na False, test nie zostanie uruchomiony, więc zawsze będzie oznaczony jako xfail, nawet wtedy, gdy po uruchomieniu byłby XPASS’em.
strict standardowo pojawienie się XPASS nie spowoduje niezaliczenia całego zestawu testów. Jeśli jednak chcemy, aby zaliczenie testu, w przypadku którego oczekiwaliśmy statusu fail, oznaczało niezliczenie całego zestawu testów, należy tę flagę ustawić na True.
raises jako wartość przyjmuje konkretny wyjątek (np. ZeroDivisionError). Tylko testy, które wygenerują ten wyjątek będą oznaczone jako xfail, natomiast pozostałe w przypadku błędnego wyniku będą oznaczone jako FAILED i wpłyną na wynik całego zestawu testów.
reason podobnie jak w powyższych parkerach, możemy tam podać string, który w przyszłości może nam pomóc w zrozumieniu działania,
conditon działanie tej flagi jest podobne do działania w skipif, jednak w przypadku jej użycie jest wymagana dla reason 

Dynamiczne markowanie testów

Kolejną funkcjonalnością jest oznaczanie testów bezpośrednio w czasie ich wykonywania. Jest to szczególnie przydatne przy parametryzacji testów. Załóżmy, że nasz kalkulator BasicCalculator może obsługiwać tylko liczby dodatnie. Tworząc test, możemy zabezpieczyć się przed podaniem niepoprawnych danych, poprzez odpowiednie oznaczenie testu z niepoprawnymi danymi. Spróbujmy oznaczyć takie testy markerem xfail, poprzez dodanie pierwszych dwóch linii w funkcji testowej test_addition:

  1. @pytest.mark.parametrize("num1, num2", addition_data)
  2. def test_addition(num1, num2):
  3. if num1 < 0 or num2 < 0:
  4. pytest.xfail("Test is valid only for positive numbers")
  5. object = BasicCalculator()
  6. object.provide_number(num1)
  7. object.provide_operand('+')
  8. object.provide_number(num2)
  9. assert object.show_result()[0] == num1 + num2

Teraz przy próbie uruchomienia testów otrzymamy odpowiednią informację, z podaniem powodu (argument dla pytest.xfail):

qabrio@test:~/basic_calculator$ pytest -v test_calc.py::test_addition
================================================ 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] PASSED [ 33%]
test_calc.py::test_addition[2--4] xfail [ 66%]
test_calc.py::test_addition[-10-12] xfail [100%]
======================================== 1 passed, 2 xfailed in 0.02 seconds ========================================
qabrio@test:~/basic_calculator$

W ten sam sposób możemy pominąć testy, wykorzystując marker skip zamiast xfail. W przypadku takiego użycia, mamy dostępne jeszcze inne działania, które możemy wykorzystać w teście:

– exit — testy są przerywane. Jeśli jednak wszystkie wcześniejsze testy były pozytywne, rezultat całego test runu będzie pozytywny.

qabrio@test:~/basic_calculator$ pytest -vr s test_calc.py::test_addition
================================================ 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] PASSED [ 33%]
test_calc.py::test_addition[2--4]
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Exit: Test is valid only for positive numbers !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================= 1 passed in 0.14 seconds ==============================================
qabrio@test:~/basic_calculator$

fail testy z tym oznaczeniem będą miały status FAILED, a jako przyczyna będzie podany tekst, który wprowadziliśmy jako argument funkcji.

===================================================== FAILURES ======================================================
________________________________________________ test_addition[2--4] ________________________________________________
num1 = 2, num2 = -4
@pytest.mark.addition
@pytest.mark.parametrize("num1, num2", addition_data)
#@pytest.mark.parametrize('addition', [data_1, data_2, data_3], ids=['test_1', '2', '3'])
#@pytest.mark.parametrize("num1", [1, 2, 3])
#@pytest.mark.parametrize("num2", [4, 5, 6])
def test_addition(num1, num2):
if num1 < 0 or num2 < 0:
> pytest.fail(msg="Test is valid only for positive numbers")
E Failed: Test is valid only for positive numbers

Wydawać by się mogło, że sprawa z markerami jest prosta, jednak kiedy przyjrzyjmy się im bliżej, widać jak wiele dają możliwości. W tej części przytoczyłem według mnie najważniejsze z nich. W kolejnej części omówię temat tworzenia własnych markerów dla testów.

pyTest Tutorial #7: Markery wbudowane i własne image

Dodaj komentarz