PyTest-vejledning: Hvad er PyTest? (med eksempler)
โก Smart opsummering
Pytest er en Python Testframework, der hjรฆlper dig med at skrive enkle, skalerbare tests til databaser, API'er og brugergrรฆnseflader. Det understรธtter fixtures, parametrisering, markรธrer, parallelle kรธrsel og detaljeret assertionrapportering, fra grundlรฆggende enhedstests til komplekse funktionelle tests.

Pytest er et af de mest anvendte testframeworks inden for Python รธkosystem. Det holder testkoden kort og lรฆsbar, registrerer tests automatisk og udvikler sig fra en fรธrste assertion til en komplet testsuite til databaser, API'er og brugergrรฆnseflader. Afsnittene nedenfor gennemgรฅr installation, assertions, fixtures, markรธrer, parametrisering og parallelle kรธrsel med praktiske eksempler.
Hvad er Pytest?
Pytest er et testframework, der lader dig skrive testkode ved hjรฆlp af Python programmeringssprog. Det hjรฆlper dig med at skrive simple og skalerbare testcases til databaser, API'er og endda brugergrรฆnseflader. Pytest bruges primรฆrt til at skrive tests til API'er, og det skalerer fra simple enhedstests til komplekse funktionelle tests.
Hvorfor bruge Pytest?
Nogle af fordelene ved pytest er:
- Meget nemt at starte med pรฅ grund af dens enkle og rene syntaks.
- Kan kรธre test parallelt.
- Kan kรธre en specifik test eller en delmรฆngde af tests.
- Registrerer automatisk tests.
- Kan springe prรธver over.
- ร ben kilde.
Sรฅdan installeres Pytest
Fรธlgende er processen for at installere pytest:
Trin 1) Du kan installere pytest med kommandoen nedenfor.
pip install pytest==2.9.1
Nรฅr installationen er fรฆrdig, kan du bekrรฆfte den med fรธlgende kommando.
py.test -h
Dette vil vise hjรฆlpen.
Fรธrste grundlรฆggende Pytest-eksempel
Nu skal du lรฆre, hvordan du bruger pytest med et grundlรฆggende pytest-eksempel.
Opret en mappe med navnet study_pytest. Du skal oprette testfilerne i denne mappe. Naviger venligst til den pรฅgรฆldende mappe i din kommandolinje. Opret en fil med navnet test_sample1.py i mappen.
Tilfรธj nedenstรฅende kode i den og gem.
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"
Kรธr testen ved hjรฆlp af kommandoen nedenfor.
py.test
Du fรฅr outputtet som fรธlger.
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
Her i test_sample1.py F. stรฅr bogstavet F for fejl, og punktummet (.) stรฅr for succes. I fejlsektionen kan du se den/de fejlede metode(r) og fejllinjen. Her betyder x==y 5==6, hvilket er falsk.
Dernรฆst vil du lรฆre om pรฅstande i Pytest.
Pรฅstande i Pytest
Pytest-pรฅstande er kontroller, der returnerer enten en sand eller falsk status. I pytest, hvis en pรฅstand fejler i en testmetode, stoppes udfรธrelsen af โโden metode der. Den resterende kode i den testmetode udfรธres ikke, og pytest fortsรฆtter med den nรฆste testmetode.
Eksempler pรฅ Pytest-pรฅstande:
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.
Overvej fรธlgende.
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Placer denne kode i test_file1_method1() i stedet for assertionen nedenfor.
assert x == y,"test failed"
At kรธre testen vil give fejlen som AssertionError: Test mislykkedes x=5 y=6.
Hvordan Pytest identificerer testfiler og testmetoder
Som standard identificerer pytest kun filnavne, der starter med prรธve_ eller slutter med _prรธve som testfilerne. Vi kan dog nรฆvne andre filnavne eksplicit (forklares senere). Pytest krรฆver, at testmetodenavnene starter med prรธveAlle andre metodenavne ignoreres, selvom vi eksplicit beder om at kรธre disse metoder.
Se nogle eksempler pรฅ gyldige og ugyldige pytest-filnavne.
test_login.py - valid login_test.py - valid testlogin.py -invalid logintest.py -invalid
Bemรฆrk: Ja, vi kan eksplicit bede pytest om at vรฆlge testlogin.py og logintest.py.
Se nogle eksempler pรฅ gyldige og ugyldige pytest-testmetoder.
def test_file1_method1(): - valid def testfile1_method1(): - valid def file1_method1(): - invalid
Bemรฆrk: Selv hvis vi eksplicit nรฆvner file1_method1(), vil pytest ikke kรธre denne metode.
Kรธr flere tests fra en specifik fil og flere filer
I รธjeblikket har vi i mappen study_pytest filen test_sample1.py. Antag, at vi har flere filer, f.eks. test_sample2.py og test_sample3.py. For at kรธre alle testene fra alle filerne i mappen og undermapperne skal vi blot kรธre kommandoen pytest.
py.test
Dette vil kรธre alle filnavne, der starter med test_, og filnavne, der slutter med _test, i den mappe og undermapperne under den mappe.
For kun at kรธre tests fra en specifik fil, kan vi bruge py.test .
py.test test_sample1.py
Kรธr et delmรฆngde af hele testen med Pytest
Nogle gange รธnsker vi ikke at kรธre hele testsuiten. Pytest giver os mulighed for at kรธre specifikke tests. Vi kan gรธre det pรฅ to mรฅder:
- Grouping af testnavne ved hjรฆlp af understrengsmatchning.
- Grouping af tests foretaget af markรธrer.
Vi har allerede test_sample1.py. Opret en fil med navnet test_sample2.py og tilfรธj nedenstรฅende kode til den.
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"
Sรฅ har vi fรธlgende lige nu.
โข test_sample1.py โข test_file1_method1() โข test_file1_method2() โข test_sample2.py โข test_file2_method1() โข test_file2_method2()
Mulighed 1) Kรธr tests ved at matche delstrenge
Her skal vi kรธre kommandoen nedenfor for at kรธre alle de tests, der har metode1 i deres navn.
py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity
Sรฅ hvis du kรธrer py.test -k method1 -v, fรฅr du fรธlgende resultat.
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 ===============================
Her kan du se, mod slutningen, 2 test fravalgt af '-kmethod1', som er test_file1_method2 og test_file2_method2.
Prรธv at lรธbe med forskellige kombinationer som fรธlgende.
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'
Mulighed 2) Kรธr test med markรธrer
Pytest giver os mulighed for at indstille forskellige attributter for testmetoderne ved hjรฆlp af pytest-markรธrer, @pytest.mark. For at bruge markรธrer i testfilen skal vi importere pytest til testfilerne. Her vil vi anvende forskellige markรธrnavne pรฅ testmetoderne og kรธre specifikke tests baseret pรฅ markรธrnavnene. Vi kan definere markรธrerne pรฅ hvert testnavn ved hjรฆlp af fรธlgende.
@pytest.mark.<name>.
Vi definerer markรธrerne sรฆt1 og sรฆt2 pรฅ testmetoderne, og vi vil kรธre testen ved hjรฆlp af markรธrnavnene. Opdater testfilerne med fรธlgende kode.
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"
Vi kan kรธre den markerede test med kommandoen nedenfor.
py.test -m <name> -m <name> mentions the marker name
Kรธr py.test -m set1. Dette vil kรธre metoderne test_file1_method1, test_file2_method1 og test_file2_method2. Kรธrsel af py.test -m set2 vil kรธre test_file1_method2.
Kรธr tests parallelt med Pytest
Normalt vil en testsuite have flere testfiler og hundredvis af testmetoder, hvilket vil tage en betydelig mรฆngde tid at udfรธre. Pytest giver os mulighed for at kรธre tests parallelt. For at gรธre det skal vi fรธrst installere pytest-xdist ved at kรธre kommandoen nedenfor.
pip install pytest-xdist
Du kan kรธre testene nu med fรธlgende kommando.
py.test -n 4
Her, -n kรธrer testene med flere arbejdere. I ovenstรฅende kommando vil der vรฆre fire arbejdere til at kรธre testene.
Pytest-armaturer
Fixtures bruges, nรฅr vi vil kรธre kode fรธr hver testmetode. Sรฅ i stedet for at gentage den samme kode i hver test, definerer vi fixtures. Normalt bruges fixtures til at initialisere databaseforbindelser, sende basen URL, og sรฅ videre. En metode markeres som en pytest-fixture ved at markere den med fรธlgende.
@pytest.fixture
En testmetode kan bruge en pytest-fixture ved at nรฆvne fixturet som en inputparameter. Opret en ny fil test_basic_fixture.py med fรธlgende kode.
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"
Her:
- Vi har en fixture ved navn supply_AA_BB_CC. Denne metode returnerer en liste med tre vรฆrdier.
- Vi har tre testmetoder, der sammenligner med hver af vรฆrdierne.
Hver af testfunktionerne har et inputargument, hvis navn matcher en tilgรฆngelig fixture. Pytest kalder derefter den tilsvarende fixture-metode, og de returnerede vรฆrdier gemmes i inputargumentet, her listen [25,35,45]. Listeelementerne bruges derefter i testmetoderne til sammenligningen. Kรธr nu testen, og se resultatet.
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 =================================
Testen test_comparewithBB bestรฅr, da zz=BB=35, og de resterende to tests fejler.
Fixture-metoden har kun et omfang i den testfil, hvor den er defineret. Hvis vi forsรธger at tilgรฅ fixture-metoden i en anden testfil, fรฅr vi en fejlmeddelelse, der siger fixture. 'supply_AA_BB_CC' ikke fundet for testmetoderne i de andre filer.
For at bruge den samme fixture mod flere testfiler, opretter vi fixture-metoder i en fil kaldet conftest.py. Lad os se dette med nedenstรฅende pytest-eksempel. Opret tre filer, conftest.py, test_basic_fixture.py og test_basic_fixture2.py, med fรธlgende kode.
conftest.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 vil fรธrst sรธge efter fixturet i testfilen, og hvis det ikke findes, vil det sรธge i conftest.py. Kรธr testen med py.test -k test_comparewith -v for at fรฅ resultatet nedenfor.
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
Pytest parametriseret test
Formรฅlet med at parametrisere en test er at kรธre en test mod flere sรฆt argumenter. Vi kan gรธre dette med @pytest.mark.parametrize. Vi vil se dette med nedenstรฅende pytest-eksempel. Her vil vi sende tre argumenter til en testmetode. Denne testmetode vil tilfรธje de to fรธrste argumenter og sammenligne resultatet med det tredje argument. Opret testfilen test_addition.py med nedenstรฅende kode.
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"
Her accepterer testmetoden tre argumenter: input1, input2 og output. Den lรฆgger input1 og input2 sammen og sammenligner summen med outputtet. Lad os kรธre testen med py.test -k test_add -v og se resultatet.
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
Du kan se, at testene blev kรธrt to gange, den ene kontrollerede 5+5 ==10 og den anden kontrollerede 3+5 ==12.
test_addition.py::test_add[5-5-10] BESTร ET
test_addition.py::test_add[3-5-12] MISLYKKES
Pytest Xfail og Skip Tests
Der vil vรฆre nogle situationer, hvor vi ikke รธnsker at udfรธre en test, eller en test sag er ikke relevant pรฅ et bestemt tidspunkt. I disse situationer har vi mulighed for at xfail testen eller springe testene over. Xfailed-testen vil blive udfรธrt, men den vil ikke tรฆlle med som en del af de ikke-bestรฅede eller ikke-bestรฅede tests. Der vil ikke vรฆre nogen traceback vises, hvis testen fejler. Vi kan xfail-tests ved hjรฆlp af @pytest.mark.xfail. Spring overping En test betyder, at testen ikke vil blive udfรธrt. Vi kan springe tests over ved at bruge @pytest.mark.skip. Rediger test_addition.py med nedenstรฅende kode.
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"
Her:
- test_add_1 og test_add_2 springes over og vil ikke blive udfรธrt.
- test_add_3 og test_add_4 er xfailed. Disse tests vil blive udfรธrt og vil vรฆre en del af xfailed (ved testfejl) eller xpassed (ved testbestรฅet) testene. Der vil ikke vรฆre nogen tractilbage for fiaskoer.
- test_add_5 og test_add_6 vil blive udfรธrt, og test_add_6 vil rapportere fejl med en traceback mens test_add_5 bestรฅr.
Udfรธr testen med py.test test_addition.py -v og se resultatet.
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 =================
Resultater i XML-format
Vi kan oprette testresultater i XML-format, som vi kan sende til Continuous Integration-servere til videre behandling. Dette kan gรธres med kommandoen py.test test_sample1.py -v โjunitxml=โresult.xmlโ. result.xml vil registrere testudfรธrelsesresultatet. Find et eksempel pรฅ result.xml nedenfor.
<?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>
Fra Vi kan se i alt to tests, hvoraf den ene ikke bestod. Nedenfor kan du se detaljerne vedrรธrende hver udfรธrt test under mรฆrke.
Pytest Framework til test af en API
Nu vil vi oprette et lille pytest-framework til at teste et API. API'et, der bruges her, er et gratis et fra reqres.inDenne hjemmeside leverer blot en testbar API og gemmer ikke vores data. Her vil vi skrive nogle tests for fรธlgende:
- Lister nogle brugere.
- Log ind med brugere.
Opret nedenstรฅende filer med den givne kode. Fรธrst har conftest.py en fixture, der leverer basen URL for alle testmetoder.
import pytest @pytest.fixture def supply_url(): return "https://reqres.in/api"
Dernรฆst indeholder test_list_user.py testmetoderne til at liste gyldige og ugyldige brugere.
- test_list_valid_user tester for en gyldig brugerhentning og verificerer svaret.
- test_list_invaliduser tester for en ugyldig brugerhentning og verificerer svaret.
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
Derefter indeholder test_login_user.py testmetoderne til test af login-funktionaliteten.
- test_login_valid tester det gyldige loginforsรธg med en e-mail og adgangskode.
- test_login_no_password tester det ugyldige loginforsรธg uden at angive en adgangskode.
- test_login_no_email tester det ugyldige loginforsรธg uden at videregive en 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
Kรธr testen med py.test -v og se resultatet som fรธlger.
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
Opdater testene og prรธv forskellige output.




