Tutorial PyTest: Ce este PyTest? (cu exemple)
⚡ Rezumat inteligent
Pytest este un Python Cadru de testare care vă ajută să scrieți teste simple și scalabile pentru baze de date, API-uri și interfețe utilizator. Acesta acceptă fixări, parametrizare, markeri, rulări paralele și raportare detaliată a aserțiunilor, de la teste unitare de bază la teste funcționale complexe.

Pytest este unul dintre cele mai utilizate framework-uri de testare din Python ecosistem. Menține codul de testare scurt și ușor de citit, descoperă automat testele și se dezvoltă de la o primă aserțiune într-o suită completă de teste pentru baze de date, API-uri și interfețe utilizator. Secțiunile de mai jos prezintă instalarea, aserțiunile, fixările, markerii, parametrizarea și rulările paralele cu exemple practice.
Ce este Pytest?
Pytest este un framework de testare care vă permite să scrieți cod de testare folosind Python limbaj de programare. Vă ajută să scrieți cazuri de testare simple și scalabile pentru baze de date, API-uri și chiar interfețe utilizator. Pytest este utilizat în principal pentru scrierea de teste pentru API-uri și se scalează de la teste unitare simple la teste funcționale complexe.
De ce să folosiți Pytest?
Câteva dintre avantajele pytest sunt:
- Foarte ușor de utilizat pentru început, datorită sintaxei sale simple și clare.
- Poate rula teste în paralel.
- Poate rula un test specific sau un subset de teste.
- Detectează automat testele.
- Se pot sări peste teste.
- Sursa deschisa.
Cum se instalează Pytest
Urmează procesul de instalare a pytest-ului:
Pas 1) Puteți instala pytest cu comanda de mai jos.
pip install pytest==2.9.1
După ce instalarea este completă, o puteți confirma cu următoarea comandă.
py.test -h
Aceasta va afișa ajutorul.
Primul exemplu de bază Pytest
Acum, veți învăța cum să utilizați pytest cu un exemplu pytest de bază.
Creați un folder study_pytest. Veți crea fișierele de test în interiorul acestui folder. Navigați la acel folder în linia de comandă. Creați un fișier numit test_sample1.py în interiorul folderului.
Adăugați codul de mai jos în el și salvați.
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"
Rulați testul folosind comanda de mai jos.
py.test
Veți obține rezultatul după cum urmează.
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
Aici, în test_sample1.py F., litera F indică eșec, iar punctul (.) indică succes. În secțiunea de eșecuri, puteți vedea metoda (metodele) eșuată(e) și linia de eșec. Aici x==y înseamnă 5==6, ceea ce este fals.
În continuare, veți învăța despre aserțiuni în pytest.
Afirmații în Pytest
Aserțiunile Pytest sunt verificări care returnează fie starea Adevărat, fie Fals. În pytest, dacă o aserțiune eșuează într-o metodă de testare, atunci execuția acelei metode este oprită acolo. Codul rămas din acea metodă de testare nu este executat, iar pytest continuă cu următoarea metodă de testare.
Exemple de aserțiuni 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.
Luați în considerare următoarele.
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Plasați acest cod în test_file1_method1() în loc de aserțiunea de mai jos.
assert x == y,"test failed"
Rularea testului va da eșecul ca AssertionError: testul eșuat x=5 y=6.
Cum identifică Pytest fișierele de testare și metodele de testare
În mod implicit, pytest identifică doar numele fișierelor care încep cu Test_ sau se termină cu _Test ca fișiere de test. Putem menționa explicit și alte nume de fișiere (explicații mai târziu). Pytest necesită ca numele metodelor de testare să înceapă cu testToate celelalte nume de metode sunt ignorate, chiar dacă solicităm explicit rularea acelor metode.
Vedeți câteva exemple de nume de fișiere pytest valide și nevalide.
test_login.py - valid login_test.py - valid testlogin.py -invalid logintest.py -invalid
Notă: Da, putem solicita în mod explicit lui pytest să selecteze testlogin.py și logintest.py.
Vedeți câteva exemple de metode de testare pytest valide și nevalide.
def test_file1_method1(): - valid def testfile1_method1(): - valid def file1_method1(): - invalid
Notă: Chiar dacă menționăm explicit file1_method1(), pytest nu va rula această metodă.
Rulați mai multe teste dintr-un anumit fișier și mai multe fișiere
În prezent, în folderul study_pytest, avem fișierul test_sample1.py. Să presupunem că avem mai multe fișiere, de exemplu test_sample2.py și test_sample3.py. Pentru a rula toate testele din toate fișierele din folder și subfoldere, trebuie doar să rulăm comanda pytest.
py.test
Aceasta va rula toate numele de fișiere care încep cu test_ și numele de fișiere care se termină cu _test din acel folder și din subfolderele din acel folder.
Pentru a rula teste doar dintr-un anumit fișier, putem folosi py.test .
py.test test_sample1.py
Executați un subset al întregului test cu Pytest
Uneori nu dorim să rulăm întreaga suită de teste. Pytest ne permite să rulăm teste specifice. Putem face acest lucru în două moduri:
- Grouping a numelor de teste prin potrivirea subșirurilor.
- Grouping a testelor prin corectori.
Avem deja fișierul test_sample1.py. Creați un fișier test_sample2.py și adăugați codul de mai jos în el.
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"
Deci avem următoarele în prezent.
• test_sample1.py • test_file1_method1() • test_file1_method2() • test_sample2.py • test_file2_method1() • test_file2_method2()
Opțiunea 1) Executați teste prin potrivirea subșirurilor
Aici, pentru a rula toate testele care au metoda1 în nume, trebuie să rulăm comanda de mai jos.
py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity
Deci, rularea comenzii py.test -k method1 -v vă va oferi următorul rezultat.
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 ===============================
Aici puteți vedea, spre sfârșit, 2 teste deselectate de „-kmethod1”, care sunt test_file1_method2 și test_file2_method2.
Încearcă să rulezi cu diverse combinații, cum ar fi următoarele.
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'
Opțiunea 2) Rulați teste prin markeri
Pytest ne permite să setăm diverse atribute pentru metodele de testare folosind markere pytest, @pytest.mark. Pentru a utiliza markere în fișierul de test, trebuie să importăm pytest în fișierele de test. Aici vom aplica diferite nume de markere metodelor de testare și vom rula teste specifice pe baza numelor markerelor. Putem defini markerele pentru fiecare nume de test folosind următoarele.
@pytest.mark.<name>.
Definim markerii set1 și set2 pe metodele de testare și vom rula testul folosind numele markerilor. Actualizați fișierele de test cu următorul cod.
test_sample1.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_sample2.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"
Putem rula testul marcat cu comanda de mai jos.
py.test -m <name> -m <name> mentions the marker name
Rulează comanda py.test -m set1. Aceasta va rula metodele test_file1_method1, test_file2_method1 și test_file2_method2. Rularea comenzii py.test -m set2 va rula comanda test_file1_method2.
Executarea testelor în paralel cu Pytest
De obicei, o suită de teste va avea mai multe fișiere de testare și sute de metode de testare, a căror execuție va dura considerabil. Pytest ne permite să rulăm teste în paralel. Pentru aceasta, trebuie mai întâi să instalăm pytest-xdist rulând comanda de mai jos.
pip install pytest-xdist
Puteți rula testele acum cu următoarea comandă.
py.test -n 4
Aici, -n execută testele folosind mai mulți workeri. În comanda de mai sus, vor exista patru workeri pentru a executa testele.
Pytest Fixtures
Fixările sunt folosite atunci când dorim să rulăm un anumit cod înainte de fiecare metodă de testare. Așadar, în loc să repetăm același cod în fiecare test, definim fixări. De obicei, fixările sunt folosite pentru a inițializa conexiunile la baza de date, a transmite codul de bază URL, și așa mai departe. O metodă este marcată ca fixtură pytest prin marcarea ei cu următoarele.
@pytest.fixture
O metodă de testare poate utiliza o fixtură pytest menționând fixtura ca parametru de intrare. Creați un fișier nou test_basic_fixture.py cu următorul cod.
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"
Aici:
- Avem un dispozitiv de fixare numit supply_AA_BB_CC. Această metodă returnează o listă de trei valori.
- Avem trei metode de testare care compară fiecare dintre valori.
Fiecare dintre funcțiile de test are un argument de intrare al cărui nume se potrivește cu un fixture disponibil. Pytest invocă apoi metoda fixture corespunzătoare, iar valorile returnate sunt stocate în argumentul de intrare, aici lista [25,35,45]. Elementele listei sunt apoi utilizate în metodele de testare pentru comparație. Acum rulați testul și vedeți rezultatul.
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 =================================
Testul test_comparewithBB reușește, deoarece zz=BB=35, iar celelalte două teste eșuează.
Metoda fixture are un domeniu de aplicare doar în fișierul de test în care este definită. Dacă încercăm să accesăm fixture-ul în alt fișier de test, vom primi o eroare care spune fixture „supply_AA_BB_CC” nu a fost găsit pentru metodele de testare din celelalte fișiere.
Pentru a utiliza aceeași fixtură pentru mai multe fișiere de test, creăm metode fixture într-un fișier numit conftest.py. Să vedem acest lucru cu exemplul pytest de mai jos. Creați trei fișiere, conftest.py, test_basic_fixture.py și test_basic_fixture2.py, cu următorul cod.
concurs.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 va căuta mai întâi fixtura în fișierul de test, iar dacă nu o găsește, o va căuta în conftest.py. Rulați testul cu comanda py.test -k test_comparewith -v pentru a obține rezultatul de mai jos.
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
Testul parametrizat Pytest
Scopul parametrizării unui test este de a rula un test pe mai multe seturi de argumente. Putem face acest lucru cu @pytest.mark.parametrize. Vom vedea acest lucru în exemplul pytest de mai jos. Aici vom transmite trei argumente unei metode de testare. Această metodă de testare va adăuga primele două argumente și va compara rezultatul cu al treilea argument. Creați fișierul de test test_addition.py cu codul de mai jos.
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"
Aici, metoda de testare acceptă trei argumente: input1, input2 și output. Adună input1 și input2 și compară suma cu output-ul. Să rulăm testul cu py.test -k test_add -v și să vedem rezultatul.
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
Puteți vedea testele rulate de două ori, unul verificând 5+5 ==10 și celălalt verificând 3+5 ==12.
test_addition.py::test_add[5-5-10] A TRUS
test_addition.py::test_add[3-5-12] A ESCUT
Pytest Xfail și teste de omitere
Vor exista unele situații în care nu dorim să executăm un test sau un caz de testare nu este relevant la un moment dat. În aceste situații, avem opțiunea de a eșua testul sau de a sări peste teste. Testul eșuat va fi executat, dar nu va fi considerat ca parte a testelor eșuate sau reușite. Nu va exista nicio traceback afișat dacă testul respectiv eșuează. Putem face teste xfail folosind @pytest.mark.xfail. Sari pesteping Un test înseamnă că testul nu va fi executat. Putem sări peste teste folosind @pytest.mark.skip. Editați fișierul test_addition.py cu codul de mai jos.
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"
Aici:
- test_add_1 și test_add_2 sunt ignorate și nu vor fi executate.
- test_add_3 și test_add_4 sunt xfailed. Aceste teste vor fi executate și vor face parte din testele xfailed (la eșecul testului) sau xpassed (la succesul testului). Nu va exista nicio traceback pentru eșecuri.
- test_add_5 și test_add_6 vor fi executate, iar test_add_6 va raporta o eroare cu un traceback în timp ce test_add_5 trece.
Executați testul cu comanda py.test test_addition.py -v și vedeți rezultatul.
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 =================
Rezultate în format XML
Putem crea rezultate ale testelor în format XML, pe care le putem transmite serverelor de integrare continuă pentru procesare ulterioară. Acest lucru se poate face cu comanda py.test test_sample1.py -v –junitxml=”result.xml”. Fișierul result.xml va înregistra rezultatul execuției testului. Găsiți un exemplu de fișier result.xml mai jos.
<?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>
Din Putem vedea un total de două teste, dintre care unul a eșuat. Mai jos, puteți vedea detaliile referitoare la fiecare test executat în cadrul etichetă.
Framework-ul Pytest pentru testarea unei API-uri
Acum vom crea un mic framework pytest pentru a testa o API. API-ul folosit aici este unul gratuit de la reqres.inAcest site web oferă pur și simplu o API testabilă și nu stochează datele noastre. Aici vom scrie câteva teste pentru următoarele:
- Listând câțiva utilizatori.
- Autentificare cu utilizatori.
Creați fișierele de mai jos cu codul dat. Mai întâi, conftest.py are un dispozitiv fix care va furniza codul de bază URL pentru toate metodele de testare.
import pytest @pytest.fixture def supply_url(): return "https://reqres.in/api"
Apoi, test_list_user.py conține metodele de testare pentru listarea utilizatorilor valizi și invalidi.
- test_list_valid_user testează o funcție fetch validă a utilizatorului și verifică răspunsul.
- test_list_invaliduser testează pentru o funcție fetch invalidă a utilizatorului și verifică răspunsul.
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
Apoi, test_login_user.py conține metodele de testare pentru testarea funcționalității de autentificare.
- test_login_valid testează încercarea validă de conectare cu o adresă de e-mail și o parolă.
- test_login_no_password testează încercarea invalidă de conectare fără a introduce o parolă.
- test_login_no_email testează încercarea invalidă de conectare fără a transmite o adresă de 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
Rulați testul folosind comanda py.test -v și vedeți rezultatul după cum urmează.
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
Actualizați testele și încercați diverse rezultate.




