Pytest - Szybki przewodnik

Pytest to platforma testowa oparta na języku Python, która służy do pisania i wykonywania kodów testowych. W dzisiejszych czasach usług REST pytest jest używany głównie do testowania API, chociaż możemy używać pytest do pisania prostych do złożonych testów, tj. Możemy pisać kody do testowania API, bazy danych, UI itp.

Zalety Pytesta

Zalety Pytest są następujące -

  • Pytest może uruchamiać wiele testów równolegle, co skraca czas wykonywania zestawu testów.

  • Pytest ma swój własny sposób wykrywania pliku testowego i automatycznego testowania funkcji, jeśli nie jest to wyraźnie wymienione.

  • Pytest pozwala nam pominąć podzbiór testów podczas wykonywania.

  • Pytest pozwala nam uruchomić podzbiór całego zestawu testów.

  • Pytest jest darmowy i open source.

  • Ze względu na prostą składnię pytest jest bardzo łatwy do rozpoczęcia.

W tym samouczku wyjaśnimy podstawowe zagadnienia pytest za pomocą przykładowych programów.

W tym rozdziale dowiemy się, jak zainstalować pytest.

Aby rozpocząć instalację, wykonaj następujące polecenie -

pip install pytest == 2.9.1

Możemy zainstalować dowolną wersję pytest. Tutaj 2.9.1 to wersja, którą instalujemy.

Aby zainstalować najnowszą wersję pytest, wykonaj następujące polecenie -

pip install pytest

Potwierdź instalację, używając następującego polecenia, aby wyświetlić sekcję pomocy pytest.

pytest -h

Uruchomienie pytest bez podania nazwy pliku spowoduje uruchomienie wszystkich plików w formacie test_*.py lub *_test.pyw bieżącym katalogu i podkatalogach. Pytest automatycznie identyfikuje te pliki jako pliki testowe. Mycan spraw, aby pytest uruchomił inne nazwy plików, wyraźnie je wymieniając.

Pytest wymaga na początek nazw funkcji testowych test. Nazwy funkcji, które nie mają formatutest*nie są traktowane jako funkcje testowe przez pytest. Mycannot jawnie zrób pytanieest rozważ każdą funkcję nie zaczynającą się od test jako funkcja testowa.

Wykonanie testów zrozumiemy w kolejnych rozdziałach.

Teraz zaczniemy od naszego pierwszego programu pytest. Najpierw utworzymy katalog, a tym samym utworzymy nasze pliki testowe w katalogu.

Wykonajmy kroki pokazane poniżej -

  • Utwórz nowy katalog o nazwie automation i przejdź do katalogu w linii poleceń.

  • Utwórz plik o nazwie test_square.py i dodaj poniższy kod do tego pliku.

import math

def test_sqrt():
   num = 25
   assert math.sqrt(num) == 5

def testsquare():
   num = 7
   assert 7*7 == 40

def tesequality():
   assert 10 == 11

Uruchom test za pomocą następującego polecenia -

pytest

Powyższe polecenie wygeneruje następujące dane wyjściowe -

test_square.py .F
============================================== FAILURES 
==============================================
______________________________________________ testsquare 
_____________________________________________
   def testsquare():
   num=7
>  assert 7*7 == 40
E  assert (7 * 7) == 40
test_square.py:9: AssertionError
================================= 1 failed, 1 passed in 0.06 seconds 
=================================

Zobacz pierwszą linię wyniku. Wyświetla nazwę pliku i wyniki. F oznacza niepowodzenie testu, a kropka (.) Oznacza sukces testu.

Poniżej możemy zobaczyć szczegóły nieudanych testów. Pokaże, przy której instrukcji test się nie powiódł. W naszym przykładzie 7 * 7 jest porównywane pod względem równości z 40, co jest błędne. Na koniec możemy zobaczyć podsumowanie wykonania testu, 1 nie powiódł się i 1 przeszedł.

Funkcja tesequality nie jest wykonywana, ponieważ pytest nie uzna jej za test, ponieważ jej nazwa nie ma formatu test*.

Teraz wykonaj poniższe polecenie i ponownie zobacz wynik -

pytest -v

-v zwiększa szczegółowość.

test_square.py::test_sqrt PASSED
test_square.py::testsquare FAILED
============================================== FAILURES 
==============================================
_____________________________________________ testsquare 
_____________________________________________
   def testsquare():
   num = 7
>  assert 7*7 == 40
E  assert (7 * 7) == 40
test_square.py:9: AssertionError
================================= 1 failed, 1 passed in 0.04 seconds 
=================================

Teraz wynik jest bardziej wyjaśniający, jeśli chodzi o test, który się nie powiódł i test, który przeszedł.

Note - polecenie pytest wykona wszystkie pliki formatu test_* lub *_test w bieżącym katalogu i podkatalogach.

W tym rozdziale nauczymy się, jak wykonać pojedynczy plik testowy i wiele plików testowych. Mamy już plik testowytest_square.pyUtworzony. Utwórz nowy plik testowytest_compare.py z następującym kodem -

def test_greater():
   num = 100
   assert num > 100

def test_greater_equal():
   num = 100
   assert num >= 100

def test_less():
   num = 100
   assert num < 200

Teraz, aby uruchomić wszystkie testy ze wszystkich plików (tutaj 2 plików), musimy uruchomić następujące polecenie -

pytest -v

Powyższe polecenie uruchomi testy z obu test_square.py i test_compare.py. Dane wyjściowe zostaną wygenerowane w następujący sposób -

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
test_compare.py::test_less PASSED
test_square.py::test_sqrt PASSED
test_square.py::testsquare FAILED
================================================ FAILURES 
================================================
______________________________________________ test_greater 
______________________________________________
   def test_greater():
   num = 100
>  assert num > 100
E  assert 100 > 100

test_compare.py:3: AssertionError
_______________________________________________ testsquare 
_______________________________________________
   def testsquare():
   num = 7
>  assert 7*7 == 40
E  assert (7 * 7) == 40

test_square.py:9: AssertionError
=================================== 2 failed, 3 passed in 0.07 seconds 
===================================

Aby wykonać testy z określonego pliku, użyj następującej składni -

pytest <filename> -v

Teraz uruchom następujące polecenie -

pytest test_compare.py -v

Powyższe polecenie wykona testy tylko z pliku test_compare.py. Nasz wynik będzie -

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
test_compare.py::test_less PASSED
============================================== FAILURES 
==============================================
____________________________________________ test_greater 
____________________________________________
   def test_greater():
   num = 100
>  assert num > 100
E  assert 100 > 100
test_compare.py:3: AssertionError
================================= 1 failed, 2 passed in 0.04 seconds 
=================================

W prawdziwym scenariuszu będziemy mieć wiele plików testowych, a każdy plik będzie miał kilka testów. Testy obejmą różne moduły i funkcjonalności. Załóżmy, że chcemy uruchomić tylko określony zestaw testów; jak to robimy?

Pytest udostępnia dwa sposoby uruchamiania podzbioru zestawu testów.

  • Wybierz testy do uruchomienia na podstawie dopasowania podciągów nazw testów.
  • Wybierz grupy testów do uruchomienia na podstawie zastosowanych znaczników.

Wyjaśnimy te dwa przykłady w kolejnych rozdziałach.

Aby wykonać testy zawierające w nazwie ciąg znaków, możemy użyć następującej składni -

pytest -k <substring> -v

-k <podłańcuch> reprezentuje podciąg do wyszukania w nazwach testów.

Teraz uruchom następujące polecenie -

pytest -k great -v

Spowoduje to wykonanie wszystkich nazw testów zawierających to słowo ‘great’w jego imieniu. W tym przypadku tak jesttest_greater() i test_greater_equal(). Zobacz wynik poniżej.

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
============================================== FAILURES 
==============================================
____________________________________________ test_greater 
____________________________________________
def test_greater():
num = 100
>  assert num > 100
E  assert 100 > 100
test_compare.py:3: AssertionError
========================== 1 failed, 1 passed, 3 deselected in 0.07 seconds 
==========================

Tutaj w wyniku możemy zobaczyć odznaczone 3 testy. Dzieje się tak, ponieważ te nazwy testów nie zawierają słowagreat w nich.

Note - Nazwa funkcji testowej powinna nadal zaczynać się od „test”.

W tym rozdziale dowiemy się, jak grupować testy za pomocą markerów.

Pytest pozwala nam używać markerów w funkcjach testowych. Markery służą do ustawiania różnych cech / atrybutów do testowania funkcji. Pytest zapewnia wiele wbudowanych markerów, takich jak xfail, skip i parametize. Oprócz tego użytkownicy mogą tworzyć własne nazwy znaczników. Markery są stosowane w testach przy użyciu składni podanej poniżej -

@pytest.mark.<markername>

Aby używać markerów, musimy import pytestmoduł w pliku testowym. Możemy zdefiniować własne nazwy markerów do testów i uruchomić testy z tymi nazwami markerów.

Aby uruchomić zaznaczone testy, możemy użyć następującej składni -

pytest -m <markername> -v

-m <markername> reprezentuje nazwę znacznika testów do wykonania.

Zaktualizuj nasze pliki testowe test_compare.py i test_square.pyz następującym kodem. Definiujemy 3 markery– great, square, others.

test_compare.py

import pytest
@pytest.mark.great
def test_greater():
   num = 100
   assert num > 100

@pytest.mark.great
def test_greater_equal():
   num = 100
   assert num >= 100

@pytest.mark.others
def test_less():
   num = 100
   assert num < 200

test_square.py

import pytest
import math

@pytest.mark.square
def test_sqrt():
   num = 25
   assert math.sqrt(num) == 5

@pytest.mark.square
def testsquare():
   num = 7
   assert 7*7 == 40

@pytest.mark.others
   def test_equality():
   assert 10 == 11

Teraz do uruchomienia testów oznaczonych jako others, uruchom następujące polecenie -

pytest -m others -v

Zobacz wynik poniżej. Uruchomił 2 testy oznaczone jakoothers.

test_compare.py::test_less PASSED
test_square.py::test_equality FAILED
============================================== FAILURES
==============================================
___________________________________________ test_equality
____________________________________________
   @pytest.mark.others
   def test_equality():
>  assert 10 == 11
E  assert 10 == 11
test_square.py:16: AssertionError
========================== 1 failed, 1 passed, 4 deselected in 0.08 seconds
==========================

Podobnie możemy przeprowadzić testy z innymi markerami - świetnie, porównaj

Urządzenia to funkcje, które będą uruchamiane przed każdą funkcją testową, do której są stosowane. Urządzenia są używane do dostarczania niektórych danych do testów, takich jak połączenia z bazami danych, adresy URL do testowania i pewne dane wejściowe. Dlatego zamiast uruchamiać ten sam kod dla każdego testu, możemy dołączyć do testów funkcję fixture, która uruchomi się i zwróci dane do testu przed wykonaniem każdego testu.

Funkcja jest oznaczona jako urządzenie przez -

@pytest.fixture

Funkcja testowa może używać urządzenia, podając nazwę urządzenia jako parametr wejściowy.

Utwórz plik test_div_by_3_6.py i dodaj do niego poniższy kod

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

Tutaj mamy nazwaną funkcję urządzenia input_value, który dostarcza dane wejściowe do testów. Aby uzyskać dostęp do funkcji urządzenia, testy muszą podawać nazwę urządzenia jako parametr wejściowy.

Pytest podczas wykonywania testu zobaczy nazwę urządzenia jako parametr wejściowy. Następnie wykonuje funkcję urządzenia, a zwrócona wartość jest przechowywana w parametrze wejściowym, który może być użyty w teście.

Wykonaj test za pomocą następującego polecenia -

pytest -k divisible -v

Powyższe polecenie wygeneruje następujący wynik -

test_div_by_3_6.py::test_divisible_by_3 PASSED
test_div_by_3_6.py::test_divisible_by_6 FAILED
============================================== FAILURES
==============================================
________________________________________ test_divisible_by_6
_________________________________________
input_value = 39
   def test_divisible_by_6(input_value):
>  assert input_value % 6 == 0
E  assert (39 % 6) == 0
test_div_by_3_6.py:12: AssertionError
========================== 1 failed, 1 passed, 6 deselected in 0.07 seconds
==========================

Jednak podejście to ma swoje własne ograniczenia. Funkcja fixture zdefiniowana w pliku testowym ma zasięg tylko w pliku testowym. Nie możemy użyć tego urządzenia w innym pliku testowym. Aby urządzenie było dostępne dla wielu plików testowych, musimy zdefiniować funkcję fixture w pliku o nazwie conftest.py.conftest.py jest wyjaśnione w następnym rozdziale.

Możemy zdefiniować funkcje urządzenia w tym pliku, aby były dostępne w wielu plikach testowych.

Utwórz nowy plik conftest.py i dodaj do niego poniższy kod -

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

Edytuj plik test_div_by_3_6.py aby usunąć funkcję mocowania -

import pytest

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

Utwórz nowy plik test_div_by_13.py -

import pytest

def test_divisible_by_13(input_value):
   assert input_value % 13 == 0

Teraz mamy pliki test_div_by_3_6.py i test_div_by_13.py wykorzystując uchwyt zdefiniowany w conftest.py.

Uruchom testy, wykonując następujące polecenie -

pytest -k divisible -v

Powyższe polecenie wygeneruje następujący wynik -

test_div_by_13.py::test_divisible_by_13 PASSED
test_div_by_3_6.py::test_divisible_by_3 PASSED
test_div_by_3_6.py::test_divisible_by_6 FAILED
============================================== FAILURES
==============================================
________________________________________ test_divisible_by_6
_________________________________________
input_value = 39
   def test_divisible_by_6(input_value):
>  assert input_value % 6 == 0
E  assert (39 % 6) == 0
test_div_by_3_6.py:7: AssertionError
========================== 1 failed, 2 passed, 6 deselected in 0.09 seconds
==========================

Testy będą szukać urządzenia w tym samym pliku. Ponieważ urządzenie nie zostało znalezione w pliku, sprawdzi je w pliku conftest.py. Po znalezieniu wywoływana jest metoda fixture, a wynik jest zwracany do argumentu wejściowego testu.

Parametryzacja testu jest wykonywana w celu uruchomienia testu dla wielu zestawów danych wejściowych. Możemy to zrobić za pomocą następującego znacznika -

@pytest.mark.parametrize

Skopiuj poniższy kod do pliku o nazwie test_multiplication.py -

import pytest

@pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)])
def test_multiplication_11(num, output):
   assert 11*num == output

Tutaj test mnoży wejście przez 11 i porównuje wynik z oczekiwanym wyjściem. Test ma 4 zestawy wejść, każdy ma 2 wartości - jeden to liczba do pomnożenia przez 11, a drugi to oczekiwany wynik.

Wykonaj test, uruchamiając następujące polecenie -

Pytest -k multiplication -v

Powyższe polecenie wygeneruje następujące dane wyjściowe -

test_multiplication.py::test_multiplication_11[1-11] PASSED
test_multiplication.py::test_multiplication_11[2-22] PASSED
test_multiplication.py::test_multiplication_11[3-35] FAILED
test_multiplication.py::test_multiplication_11[4-44] PASSED
============================================== FAILURES
==============================================
_________________ test_multiplication_11[3-35] __________________
num = 3, output = 35
   @pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)])
   def test_multiplication_11(num, output):
>  assert 11*num == output
E  assert (11 * 3) == 35
test_multiplication.py:5: AssertionError
============================== 1 failed, 3 passed, 8 deselected in 0.08 seconds
==============================

W tym rozdziale dowiemy się o testach Skip i Xfail w Pytest.

Rozważmy teraz poniższe sytuacje -

  • Z pewnych powodów test nie jest istotny przez pewien czas.
  • Wdrażana jest nowa funkcja i dodaliśmy już test dla tej funkcji.

W takich sytuacjach mamy możliwość xfail testu lub pominięcia testów.

Pytest wykona test xfailed, ale nie zostanie on uznany za częściowo niezaliczony lub zaliczony. Szczegóły tych testów nie zostaną wydrukowane, nawet jeśli test się nie powiedzie (pamiętaj, pytest zwykle wypisuje szczegóły testu, który zakończył się niepowodzeniem). Możemy xfail testy przy użyciu następującego znacznika -

@pytest.mark.xfail

Pominięcie testu oznacza, że ​​test nie zostanie wykonany. Możemy pominąć testy za pomocą następującego markera -

@pytest.mark.skip

Później, gdy test stanie się istotny, możemy usunąć znaczniki.

Edytuj plik test_compare.py musimy już uwzględnić znaczniki xfail i skip -

import pytest
@pytest.mark.xfail
@pytest.mark.great
def test_greater():
   num = 100
   assert num > 100

@pytest.mark.xfail
@pytest.mark.great
def test_greater_equal():
   num = 100
   assert num >= 100

@pytest.mark.skip
@pytest.mark.others
def test_less():
   num = 100
   assert num < 200

Wykonaj test za pomocą następującego polecenia -

pytest test_compare.py -v

Po wykonaniu powyższe polecenie wygeneruje następujący wynik -

test_compare.py::test_greater xfail
test_compare.py::test_greater_equal XPASS
test_compare.py::test_less SKIPPED
============================ 1 skipped, 1 xfailed, 1 xpassed in 0.06 seconds
============================

W prawdziwym scenariuszu, gdy nowa wersja kodu jest gotowa do wdrożenia, jest najpierw wdrażana w środowisku pre-prod / staging. Następnie uruchamia się na nim zestaw testów.

Kod jest kwalifikowany do wdrożenia w środowisku produkcyjnym tylko wtedy, gdy pakiet testów przejdzie pomyślnie. Jeśli wystąpi błąd testu, niezależnie od tego, czy jest to jeden, czy wiele, kod nie jest gotowy do produkcji.

A więc co, jeśli chcemy zatrzymać wykonywanie zestawu testów wkrótce po n liczbie niepowodzeń. Można to zrobić w pytest używając maxfail.

Składnia zatrzymania wykonywania zestawu testów wkrótce po n liczbie niepowodzeń testu jest następująca:

pytest --maxfail = <num>

Utwórz plik test_failure.py z następującym kodem.

import pytest
import math

def test_sqrt_failure():
   num = 25
   assert math.sqrt(num) == 6

def test_square_failure():
   num = 7
   assert 7*7 == 40

def test_equality_failure():
   assert 10 == 11

Wszystkie 3 testy zakończą się niepowodzeniem podczas wykonywania tego pliku testowego. Tutaj zatrzymamy wykonywanie testu po jednym niepowodzeniu przez -

pytest test_failure.py -v --maxfail = 1
test_failure.py::test_sqrt_failure FAILED
=================================== FAILURES
=================================== _______________________________________
test_sqrt_failure __________________________________________
   def test_sqrt_failure():
   num = 25
>  assert math.sqrt(num) == 6
E  assert 5.0 == 6
E  + where 5.0 = <built-in function sqrt>(25)
E  + where <built-in function sqrt>= math.sqrt
test_failure.py:6: AssertionError
=============================== 1 failed in 0.04 seconds
===============================

W powyższym wyniku widzimy, że wykonanie jest zatrzymywane przy jednej awarii.

Domyślnie pytest uruchamia testy w kolejności sekwencyjnej. W prawdziwym scenariuszu zestaw testów będzie zawierał wiele plików testowych, a każdy plik będzie zawierał kilka testów. Doprowadzi to do długiego czasu wykonania. Aby temu zaradzić, pytest daje nam możliwość równoległego uruchamiania testów.

W tym celu musimy najpierw zainstalować wtyczkę pytest-xdist.

Zainstaluj pytest-xdist, uruchamiając następujące polecenie -

pip install pytest-xdist

Teraz możemy uruchomić testy, używając składni pytest -n <num>

pytest -n 3

-n <num> uruchamia testy przy użyciu wielu pracowników, tutaj jest to 3.

Nie będziemy mieć dużej różnicy czasu, gdy do wykonania jest tylko kilka testów. Jednak ma to znaczenie, gdy zestaw testów jest duży.

Możemy wygenerować szczegóły wykonania testu w pliku xml. Ten plik xml jest przydatny głównie w przypadkach, gdy mamy pulpit nawigacyjny, który wyświetla wyniki testów. W takich przypadkach plik XML można przeanalizować w celu uzyskania szczegółów wykonania.

Wykonamy teraz testy z test_multiplcation.py i wygenerujemy plik XML, uruchamiając

pytest test_multiplication.py -v --junitxml="result.xml"

Teraz widzimy, że result.xml jest generowany z następującymi danymi -

<?xml version = "1.0" encoding = "utf-8"?>
<testsuite errors = "0" failures = "1"
name = "pytest" skips = "0" tests = "4" time = "0.061">
   <testcase classname = "test_multiplication"          
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[1-11]"
      time = "0.00117516517639>
   </testcase>
   
   <testcase classname = "test_multiplication"    
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[2-22]"
      time = "0.00155973434448">
   </testcase>

   <testcase classname = "test_multiplication" 
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[3-35]" time = "0.00144290924072">
      failure message = "assert (11 * 3) == 35">num = 3, output = 35

         @pytest.mark.parametrize("num,
         output",[(1,11),(2,22),(3,35),(4,44)])
            
         def test_multiplication_11(num, output):> 
         assert 11*num == output
         E assert (11 * 3) == 35

         test_multiplication.py:5: AssertionErro
      </failure>
   </testcase>
   <testcase classname = "test_multiplication" 
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[4-44]"
      time = "0.000945091247559">
   </testcase>
</testsuite>

Tutaj metka <testsuit> podsumowuje, że przeprowadzono 4 testy, a liczba niepowodzeń wynosi 1.

  • Tag <testcase> podaje szczegóły każdego wykonanego testu.

  • Tag <failure> zawiera szczegółowe informacje o kodzie testu zakończonego niepowodzeniem.

W tym tutorialu pytest omówiliśmy następujące obszary -

  • Instalowanie pytest ..
  • Identyfikowanie plików testowych i funkcji testowych.
  • Wykonywanie wszystkich plików testowych przy użyciu polecenia pytest –v.
  • Wykonywanie określonego pliku usimng pytest <nazwa pliku> -v.
  • Wykonaj testy, dopasowując podciąg pytest -k <podciąg> -v.
  • Wykonaj testy na podstawie znaczników pytest -m <marker_name> -v.
  • Tworzenie urządzeń przy użyciu @ pytest.fixture.
  • conftest.py umożliwia dostęp do urządzeń z wielu plików.
  • Parametryzacja testów przy użyciu @ pytest.mark.parametrize.
  • Testy Xfailing przy użyciu @ pytest.mark.xfail.
  • Pomijanie testów przy użyciu @ pytest.mark.skip.
  • Zatrzymaj wykonywanie testu dla n błędów przy użyciu pytest --maxfail = <num>.
  • Równoległe uruchamianie testów przy użyciu pytest -n <num>.
  • Generowanie wyników xml przy użyciu pytest -v --junitxml = "result.xml".

Ten samouczek wprowadził Cię w pytest framework. Teraz powinieneś być w stanie rozpocząć pisanie testów za pomocą pytest.

Jako dobra praktyka -

  • Twórz różne pliki testowe w oparciu o testowaną funkcjonalność / moduł.
  • Nadaj znaczące nazwy do testowania plików i metod.
  • Miej wystarczającą liczbę znaczników, aby pogrupować testy na podstawie różnych kryteriów.
  • W razie potrzeby używaj uchwytów.