Samouczek PyTest: Czym jest PyTest? (z przykładami)
⚡ Inteligentne podsumowanie
Pytest to jest Python Framework testowy, który pomaga pisać proste, skalowalne testy dla baz danych, interfejsów API i interfejsów użytkownika. Obsługuje on funkcje testowania, parametryzację, znaczniki, przebiegi równoległe i szczegółowe raportowanie asercji, od podstawowych testów jednostkowych po złożone testy funkcjonalne.

Pytest jest jednym z najczęściej używanych frameworków testowych Python Ekosystem. Utrzymuje kod testowy zwięzły i czytelny, automatycznie wykrywa testy i rozwija się od pierwszej asercji do pełnego zestawu testów dla baz danych, API i interfejsów użytkownika. Poniższe sekcje opisują instalację, asercje, wyposażenie, znaczniki, parametryzację i przebiegi równoległe z praktycznymi przykładami.
Czym jest Pytest?
Pytest jest środowiskiem testowym, które umożliwia pisanie kodu testowego przy użyciu Python Język programowania. Pomaga pisać proste i skalowalne przypadki testowe dla baz danych, interfejsów API, a nawet interfejsów użytkownika. Pytest jest używany głównie do pisania testów dla interfejsów API i skaluje się od prostych testów jednostkowych do złożonych testów funkcjonalnych.
Dlaczego warto używać Pytest?
Oto niektóre z zalet pytest:
- Bardzo łatwy na początek, ze względu na prostą i przejrzystą składnię.
- Można uruchamiać testy równolegle.
- Można uruchomić konkretny test lub podzbiór testów.
- Automatycznie wykrywa testy.
- Można pominąć testy.
- Otwarte źródło.
Jak zainstalować Pytest
Poniżej przedstawiono proces instalacji pytest:
Krok 1) Możesz zainstalować pytest za pomocą poniższego polecenia.
pip install pytest==2.9.1
Po zakończeniu instalacji możesz ją potwierdzić za pomocą następującego polecenia.
py.test -h
Spowoduje to wyświetlenie pomocy.
Pierwszy podstawowy przykład Pytest
Teraz nauczysz się używać pytesta na przykładzie podstawowego pytesta.
Utwórz folder study_pytest. W tym folderze utworzysz pliki testowe. Przejdź do tego folderu w wierszu poleceń. Utwórz w nim plik o nazwie test_sample1.py.
Dodaj poniższy kod i zapisz.
import pytest def test_file1_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed" def test_file1_method2(): x=5 y=6 assert x+1 == y,"test failed"
Uruchom test za pomocą poniższego polecenia.
py.test
Wynik będzie wyglądał następująco.
test_sample1.py F.
============================================== FAILURES ========================================
____________________________________________ test_sample1 ______________________________________
def test_file1_method1():
x=5
y=6
assert x+1 == y,"test failed"
> assert x == y,"test failed"
E AssertionError: test failed
E assert 5 == 6
test_sample1.py:6: AssertionError
Tutaj, w pliku test_sample1.py F., litera F oznacza niepowodzenie, a kropka (.) oznacza powodzenie. W sekcji błędów widoczne są nieudane metody i wiersz błędu. Tutaj x==y oznacza 5==6, co oznacza fałsz.
Następnie dowiesz się o asercjach w pytest.
Asercje w Pytest
Asercje w pytest to testy, które zwracają status True (prawda) lub False (fałsz). W pytest, jeśli asercja w metodzie testowej zakończy się niepowodzeniem, wykonywanie tej metody zostaje przerwane. Pozostały kod w tej metodzie testowej nie jest wykonywany, a pytest kontynuuje wykonywanie następnej metody testowej.
Przykłady asercji Pytest:
assert "hello" == "Hai" is an assertion failure. assert 4==4 is a successful assertion assert True is a successful assertion assert False is an assertion failure.
Rozważ następujące kwestie.
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Umieść ten kod w test_file1_method1() zamiast poniższego potwierdzenia.
assert x == y,"test failed"
Uruchomienie testu spowoduje niepowodzenie jako AssertionError: test nie powiódł się x=5 y=6.
Jak Pytest identyfikuje pliki testowe i metody testowe
Domyślnie pytest identyfikuje tylko nazwy plików zaczynające się od test_ lub kończąc na _test jak pliki testowe. Możemy jednak jawnie podać inne nazwy plików (wyjaśnimy to później). Pytest wymaga, aby nazwy metod testowych zaczynały się od testWszystkie inne nazwy metod są ignorowane, nawet jeśli wyraźnie poprosimy o uruchomienie tych metod.
Zobacz przykłady prawidłowych i nieprawidłowych nazw plików pytest.
test_login.py - valid login_test.py - valid testlogin.py -invalid logintest.py -invalid
Uwaga: Tak, możemy jawnie poprosić pytest o wybranie plików testlogin.py i logintest.py.
Zobacz przykłady prawidłowych i nieprawidłowych metod testowych pytest.
def test_file1_method1(): - valid def testfile1_method1(): - valid def file1_method1(): - invalid
Uwaga: Nawet jeśli wyraźnie wspomnimy o file1_method1(), pytest nie uruchomi tej metody.
Uruchom wiele testów z określonego pliku i wielu plików
Obecnie w folderze study_pytest znajduje się plik test_sample1.py. Załóżmy, że mamy wiele plików, na przykład test_sample2.py i test_sample3.py. Aby uruchomić wszystkie testy ze wszystkich plików w folderze i podfolderach, wystarczy uruchomić polecenie pytest.
py.test
Spowoduje to uruchomienie wszystkich plików o nazwach zaczynających się od test_ i kończących się na _test w danym folderze i jego podfolderach.
Aby uruchomić testy tylko z określonego pliku, możemy użyć py.test .
py.test test_sample1.py
Uruchom podzbiór całego testu za pomocą Pytest
Czasami nie chcemy uruchamiać całego zestawu testów. Pytest pozwala nam uruchamiać konkretne testy. Możemy to zrobić na dwa sposoby:
- Grouping nazw testów poprzez dopasowywanie podciągów.
- Grouping testów według markerów.
Mamy już plik test_sample1.py. Utwórz plik test_sample2.py i dodaj do niego poniższy kod.
def test_file2_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed because x=" + str(x) + " y=" + str(y) def test_file2_method2(): x=5 y=6 assert x+1 == y,"test failed"
Więc obecnie mamy następująco.
• test_sample1.py • test_file1_method1() • test_file1_method2() • test_sample2.py • test_file2_method1() • test_file2_method2()
Opcja 1) Uruchom testy poprzez dopasowanie podciągów
Tutaj, aby uruchomić wszystkie testy zawierające w nazwie method1, musimy uruchomić poniższe polecenie.
py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity
Tak więc uruchomienie polecenia py.test -k method1 -v da następujący wynik.
test_sample2.py::test_file2_method1 FAILED
test_sample1.py::test_file1_method1 FAILED
============================================== FAILURES ==============================================
_________________________________________ test_file2_method1 _________________________________________
def test_file2_method1():
x=5
y=6
assert x+1 == y,"test failed"
> assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E AssertionError: test failed because x=5 y=6
E assert 5 == 6
test_sample2.py:5: AssertionError
_________________________________________ test_file1_method1 _________________________________________
@pytest.mark.only
def test_file1_method1():
x=5
y=6
assert x+1 == y,"test failed"
> assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E AssertionError: test failed because x=5 y=6
E assert 5 == 6
test_sample1.py:8: AssertionError
================================= 2 tests deselected by '-kmethod1' ==================================
=============================== 2 failed, 2 deselected in 0.02 seconds ===============================
Tutaj możesz zobaczyć pod koniec, 2 testy odznaczono za pomocą „-kmethod1”, które są test_file1_method2 i test_file2_method2.
Wypróbuj różne kombinacje, takie jak poniżej.
py.test -k method -v - will run all the four methods py.test -k methods -v – will not run any test as there is no test name matches the substring 'methods'
Opcja 2) Uruchom testy według markerów
Pytest pozwala nam ustawiać różne atrybuty dla metod testowych za pomocą znaczników pytest, @pytest.mark. Aby użyć znaczników w pliku testowym, musimy zaimportować pytest do plików testowych. W tym miejscu zastosujemy różne nazwy znaczników do metod testowych i uruchomimy konkretne testy na podstawie tych nazw. Możemy zdefiniować znaczniki dla każdej nazwy testu, używając poniższego kodu.
@pytest.mark.<name>.
Definiujemy znaczniki set1 i set2 w metodach testowych i uruchomimy test, używając nazw znaczników. Zaktualizuj pliki testu, używając poniższego kodu.
test_przykład1.py
import pytest @pytest.mark.set1 def test_file1_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed because x=" + str(x) + " y=" + str(y) @pytest.mark.set2 def test_file1_method2(): x=5 y=6 assert x+1 == y,"test failed"
test_przykład2.py
import pytest @pytest.mark.set1 def test_file2_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed because x=" + str(x) + " y=" + str(y) @pytest.mark.set1 def test_file2_method2(): x=5 y=6 assert x+1 == y,"test failed"
Oznaczony test możemy uruchomić za pomocą poniższego polecenia.
py.test -m <name> -m <name> mentions the marker name
Uruchom py.test -m set1. Spowoduje to uruchomienie metod test_file1_method1, test_file2_method1 i test_file2_method2. Uruchomienie py.test -m set2 spowoduje uruchomienie test_file1_method2.
Uruchamianie testów równolegle z Pytest
Zazwyczaj zestaw testów zawiera wiele plików testowych i setki metod testowych, których wykonanie zajmuje dużo czasu. Pytest pozwala na równoległe uruchamianie testów. W tym celu musimy najpierw zainstalować pytest-xdist, uruchamiając poniższe polecenie.
pip install pytest-xdist
Możesz teraz uruchomić testy za pomocą następującego polecenia.
py.test -n 4
Tutaj, -n uruchamia testy przy użyciu wielu procesów roboczych. W powyższym poleceniu testy będą uruchamiane przez cztery procesy robocze.
Urządzenia Pytesta
Fixtury są używane, gdy chcemy uruchomić jakiś kod przed każdą metodą testową. Zamiast więc powtarzać ten sam kod w każdym teście, definiujemy Fixtury. Zazwyczaj Fixtury służą do inicjowania połączeń z bazą danych, przekazywania danych bazowych. URLitd. Metodę oznacza się jako element pytest, poprzez oznaczenie jej w następujący sposób.
@pytest.fixture
Metoda testowa może używać fixtury pytest, podając ją jako parametr wejściowy. Utwórz nowy plik test_basic_fixture.py z poniższym kodem.
import pytest @pytest.fixture def supply_AA_BB_CC(): aa=25 bb =35 cc=45 return [aa,bb,cc] def test_comparewithAA(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed" def test_comparewithBB(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed" def test_comparewithCC(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
Tutaj:
- Mamy obiekt o nazwie supply_AA_BB_CC. Ta metoda zwraca listę trzech wartości.
- Stosujemy trzy metody testowe porównujące każdą z wartości.
Każda z funkcji testowych ma argument wejściowy, którego nazwa odpowiada dostępnemu fixture. Następnie Pytest wywołuje odpowiednią metodę fixture, a zwrócone wartości są zapisywane w argumencie wejściowym, w tym przypadku na liście [25,35,45]. Elementy listy są następnie używane w metodach testowych do porównania. Teraz uruchom test i sprawdź wynik.
py.test test_basic_fixture
test_basic_fixture.py::test_comparewithAA FAILED
test_basic_fixture.py::test_comparewithBB PASSED
test_basic_fixture.py::test_comparewithCC FAILED
============================================== FAILURES ==============================================
_________________________________________ test_comparewithAA _________________________________________
supply_AA_BB_CC = [25, 35, 45]
def test_comparewithAA(supply_AA_BB_CC):
zz=35
> assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"
E AssertionError: aa and zz comparison failed
E assert 25 == 35
test_basic_fixture.py:10: AssertionError
_________________________________________ test_comparewithCC _________________________________________
supply_AA_BB_CC = [25, 35, 45]
def test_comparewithCC(supply_AA_BB_CC):
zz=35
> assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
E AssertionError: cc and zz comparison failed
E assert 45 == 35
test_basic_fixture.py:16: AssertionError
================================= 2 failed, 1 passed in 0.05 seconds =================================
Test test_comparewithBB kończy się pomyślnie, ponieważ zz=BB=35, a pozostałe dwa testy kończą się niepowodzeniem.
Metoda Fixture ma zasięg tylko w pliku testowym, w którym jest zdefiniowana. Jeśli spróbujemy uzyskać dostęp do Fixture w innym pliku testowym, pojawi się błąd o treści „fixture”. Nie znaleziono „supply_AA_BB_CC”. dla metod testowych w innych plikach.
Aby użyć tej samej konfiguracji dla wielu plików testowych, tworzymy metody konfiguracji w pliku conftest.py. Przeanalizujmy to na poniższym przykładzie pytest. Utwórz trzy pliki: conftest.py, test_basic_fixture.py i test_basic_fixture2.py, z następującym kodem.
konkurs.py
import pytest @pytest.fixture def supply_AA_BB_CC(): aa=25 bb =35 cc=45 return [aa,bb,cc]
test_basic_fixture.py
import pytest def test_comparewithAA(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed" def test_comparewithBB(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed" def test_comparewithCC(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
test_basic_fixture2.py
import pytest def test_comparewithAA_file2(supply_AA_BB_CC): zz=25 assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed" def test_comparewithBB_file2(supply_AA_BB_CC): zz=25 assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed" def test_comparewithCC_file2(supply_AA_BB_CC): zz=25 assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
Pytest najpierw poszuka elementu w pliku testowym, a jeśli go nie znajdzie, przeszuka plik conftest.py. Uruchom test poleceniem py.test -k test_comparewith -v, aby uzyskać poniższy wynik.
test_basic_fixture.py::test_comparewithAA FAILED test_basic_fixture.py::test_comparewithBB PASSED test_basic_fixture.py::test_comparewithCC FAILED test_basic_fixture2.py::test_comparewithAA_file2 PASSED test_basic_fixture2.py::test_comparewithBB_file2 FAILED test_basic_fixture2.py::test_comparewithCC_file2 FAILED
Test parametryczny Pytest
Celem parametryzacji testu jest uruchomienie go dla wielu zestawów argumentów. Możemy to zrobić za pomocą @pytest.mark.parametrize. Zobaczymy to na poniższym przykładzie pytest. Przekażemy tu trzy argumenty do metody testowej. Ta metoda doda pierwsze dwa argumenty i porówna wynik z trzecim argumentem. Utwórz plik testowy test_addition.py z poniższym kodem.
import pytest @pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)]) def test_add(input1, input2, output): assert input1+input2 == output,"failed"
W tym przypadku metoda testowa przyjmuje trzy argumenty: input1, input2 i output. Dodaje input1 i input2 i porównuje sumę z outputem. Uruchommy test poleceniem py.test -k test_add -v i sprawdźmy wynik.
test_addition.py::test_add[5-5-10] PASSED
test_addition.py::test_add[3-5-12] FAILED
============================================== FAILURES ==============================================
__________________________________________ test_add[3-5-12] __________________________________________
input1 = 3, input2 = 5, output = 12
@pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
def test_add(input1, input2, output):
> assert input1+input2 == output,"failed"
E AssertionError: failed
E assert (3 + 5) == 12
test_addition.py:5: AssertionError
Można zobaczyć, że testy przeprowadzono dwukrotnie: pierwszy raz sprawdzano wynik 5+5 ==10, a drugi raz 3+5 ==12.
test_addition.py::test_add[5-5-10] ZALICZONE
test_addition.py::test_add[3-5-12] NIEUDANE
Testy Pytest Xfail i Skip
Będą sytuacje, w których nie będziemy chcieli przeprowadzać testu lub walizka testowa nie jest istotne w danym momencie. W takich sytuacjach mamy możliwość zaliczenia testu z wynikiem pozytywnym lub pominięcia testów. Test z wynikiem pozytywnym zostanie wykonany, ale nie będzie zaliczany do testów z wynikiem pozytywnym lub negatywnym. Nie będzie tracW przypadku niepowodzenia testu wyświetlany jest kod eback. Możemy oznaczyć testy jako xfail za pomocą @pytest.mark.xfail. Pomińping Test oznacza, że test nie zostanie wykonany. Możemy pominąć testy, używając @pytest.mark.skip. Edytuj plik test_addition.py za pomocą poniższego kodu.
import pytest @pytest.mark.skip def test_add_1(): assert 100+200 == 400,"failed" @pytest.mark.skip def test_add_2(): assert 100+200 == 300,"failed" @pytest.mark.xfail def test_add_3(): assert 15+13 == 28,"failed" @pytest.mark.xfail def test_add_4(): assert 15+13 == 100,"failed" def test_add_5(): assert 3+2 == 5,"failed" def test_add_6(): assert 3+2 == 6,"failed"
Tutaj:
- test_add_1 i test_add_2 są pomijane i nie zostaną wykonane.
- Test_add_3 i test_add_4 są oznaczone jako xfailed. Te testy zostaną wykonane i będą częścią testów xfailed (w przypadku niepowodzenia testu) lub xpassed (w przypadku zaliczenia testu). Nie będzie żadnych traceback za porażki.
- zostaną wykonane testy test_add_5 i test_add_6, a test_add_6 zgłosi błąd z traceback, dopóki test_add_5 nie przejdzie.
Wykonaj test poleceniem py.test test_addition.py -v i zobacz wynik.
test_addition.py::test_add_1 SKIPPED
test_addition.py::test_add_2 SKIPPED
test_addition.py::test_add_3 XPASS
test_addition.py::test_add_4 xfail
test_addition.py::test_add_5 PASSED
test_addition.py::test_add_6 FAILED
============================================== FAILURES ==============================================
_____________________________________________ test_add_6 _____________________________________________
def test_add_6():
> assert 3+2 == 6,"failed"
E AssertionError: failed
E assert (3 + 2) == 6
test_addition.py:24: AssertionError
================ 1 failed, 1 passed, 2 skipped, 1 xfailed, 1 xpassed in 0.07 seconds =================
Wyniki w formacie XML
Możemy tworzyć wyniki testów w formacie XML, które następnie możemy przesłać do serwerów ciągłej integracji w celu dalszego przetwarzania. Można to zrobić za pomocą polecenia py.test test_sample1.py -v –junitxml=”result.xml”. Plik result.xml zapisze wynik wykonania testu. Przykładowy plik result.xml znajduje się poniżej.
<?xml version="1.0" encoding="UTF-8"?> <testsuite errors="0" failures="1" name="pytest" skips="0" tests="2" time="0.046"> <testcase classname="test_sample1" file="test_sample1.py" line="3" name="test_file1_method1" time="0.001384973526"> <failure message="AssertionError:test failed because x=5 y=6 assert 5 ==6"> @pytest.mark.set1 def test_file1_method1(): x=5 y=6 assert x+1 == y,"test failed" > assert x == y,"test failed because x=" + str(x) + " y=" + str(y) E AssertionError: test failed because x=5 y=6 E assert 5 == 6 test_sample1.py:9: AssertionError </failure> </testcase> <testcase classname="test_sample1" file="test_sample1.py" line="10" name="test_file1_method2" time="0.000830173492432" /> </testsuite>
Z Widzimy łącznie dwa testy, z których jeden zakończył się niepowodzeniem. Poniżej znajdują się szczegóły dotyczące każdego wykonanego testu pod etykietka.
Framework Pytest do testowania API
Teraz utworzymy mały framework pytest do testowania API. Użyte tutaj API jest darmowe i pochodzi od reqres.inTa strona internetowa udostępnia jedynie testowalny interfejs API i nie przechowuje naszych danych. Poniżej napiszemy kilka testów dla następujących elementów:
- Wyświetlanie listy niektórych użytkowników.
- Zaloguj się jako użytkownicy.
Utwórz poniższe pliki za pomocą podanego kodu. Najpierw conftest.py zawiera element, który dostarczy bazę URL dla wszystkich metod testowych.
import pytest @pytest.fixture def supply_url(): return "https://reqres.in/api"
Następnie plik test_list_user.py zawiera metody testowe służące do wyświetlania listy prawidłowych i nieprawidłowych użytkowników.
- test_list_valid_user sprawdza poprawność pobrania danych przez użytkownika i weryfikuje odpowiedź.
- test_list_invaliduser sprawdza, czy użytkownik pobrał nieprawidłowe dane i weryfikuje odpowiedź.
import pytest import requests import json @pytest.mark.parametrize("userid, firstname",[(1,"George"),(2,"Janet")]) def test_list_valid_user(supply_url,userid,firstname): url = supply_url + "/users/" + str(userid) resp = requests.get(url) j = json.loads(resp.text) assert resp.status_code == 200, resp.text assert j['data']['id'] == userid, resp.text assert j['data']['first_name'] == firstname, resp.text def test_list_invaliduser(supply_url): url = supply_url + "/users/50" resp = requests.get(url) assert resp.status_code == 404, resp.text
Następnie plik test_login_user.py zawiera metody testowe służące do testowania funkcjonalności logowania.
- test_login_valid sprawdza poprawność próby logowania przy użyciu adresu e-mail i hasła.
- test_login_no_password sprawdza, czy próba logowania bez podania hasła jest nieudana.
- test_login_no_email sprawdza, czy próba logowania jest nieudana, bez podawania adresu e-mail.
import pytest import requests import json def test_login_valid(supply_url): url = supply_url + "/login/" data = {'email':'test@test.com','password':'something'} resp = requests.post(url, data=data) j = json.loads(resp.text) assert resp.status_code == 200, resp.text assert j['token'] == "QpwL5tke4Pnpja7X", resp.text def test_login_no_password(supply_url): url = supply_url + "/login/" data = {'email':'test@test.com'} resp = requests.post(url, data=data) j = json.loads(resp.text) assert resp.status_code == 400, resp.text assert j['error'] == "Missing password", resp.text def test_login_no_email(supply_url): url = supply_url + "/login/" data = {} resp = requests.post(url, data=data) j = json.loads(resp.text) assert resp.status_code == 400, resp.text assert j['error'] == "Missing email or username", resp.text
Uruchom test za pomocą py.test -v i zobacz wynik, jak poniżej.
test_list_user.py::test_list_valid_user[1-George] PASSED test_list_user.py::test_list_valid_user[2-Janet] PASSED test_list_user.py::test_list_invaliduser PASSED test_login_user.py::test_login_valid PASSED test_login_user.py::test_login_no_password PASSED test_login_user.py::test_login_no_email PASSED
Zaktualizuj testy i wypróbuj różne wyniki.




