PyTest-handledning: Vad är PyTest? (med exempel)
⚡ Smart sammanfattning
Pytest är en Python testramverk som hjälper dig att skriva enkla, skalbara tester för databaser, API:er och användargränssnitt. Det stöder fixturer, parametrisering, markörer, parallella körningar och detaljerad assertionrapportering, från grundläggande enhetstester till komplexa funktionstester.
Pytest är ett av de mest använda testramverken inom Python ekosystem. Det håller testkoden kort och läsbar, upptäcker tester automatiskt och växer från en första assertion till en komplett testsvit för databaser, API:er och användargränssnitt. Avsnitten nedan går igenom installation, assertioner, fixturer, markörer, parametrisering och parallella körningar med praktiska exempel.
Vad är Pytest?
Pytest är ett testramverk som låter dig skriva testkod med hjälp av Python programmeringsspråk. Det hjälper dig att skriva enkla och skalbara testfall för databaser, API:er och till och med användargränssnitt. Pytest används främst för att skriva tester för API:er, och det skalar från enkla enhetstester till komplexa funktionstester.
Varför använda Pytest?
Några av fördelarna med pytest är:
- Väldigt lätt att börja med, på grund av dess enkla och rena syntax.
- Kan köra tester parallellt.
- Kan köra ett specifikt test eller en delmängd av tester.
- Upptäcker tester automatiskt.
- Kan hoppa över prov.
- Öppen källa.
Hur man installerar Pytest
Följande är processen för att installera pytest:
Steg 1) Du kan installera pytest med kommandot nedan.
pip install pytest==2.9.1
När installationen är klar kan du bekräfta den med följande kommando.
py.test -h
Detta kommer att visa hjälpen.
Första grundläggande Pytest-exemplet
Nu ska du lära dig hur du använder pytest med ett grundläggande pytest-exempel.
Skapa en mapp med namnet study_pytest. Du ska skapa testfilerna i den här mappen. Navigera till den mappen i kommandoraden. Skapa en fil med namnet test_sample1.py i mappen.
Lägg till koden nedan i den och spara.
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 testet med kommandot nedan.
py.test
Du får utdata enligt följande.
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
Här, i test_sample1.py F., står bokstaven F för misslyckande och punkten (.) för framgång. I avsnittet om misslyckanden kan du se den/de misslyckade metoden/metoderna och felraden. Här betyder x==y 5==6, vilket är falskt.
Härnäst kommer du att lära dig om påståenden i Pytest.
Påståenden i Pytest
Pytest-påståenden är kontroller som returnerar antingen statusen Sant eller Falskt. Om ett påstående i en pytest-metod misslyckas, stoppas körningen av den metoden där. Återstående kod i den testmetoden körs inte, och pytest fortsätter med nästa testmetod.
Exempel på Pytest-påståenden:
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.
Tänk på följande.
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Placera den här koden i test_file1_method1() istället för påståendet nedan.
assert x == y,"test failed"
Att köra testet ger felet som AssertionError: testet misslyckades x=5 y=6.
Hur Pytest identifierar testfiler och testmetoder
Som standard identifierar pytest endast filnamn som börjar med testa_ eller slutar med _testa som testfilerna. Vi kan dock nämna andra filnamn explicit (förklaras senare). Pytest kräver att testmetodens namn börjar med testAlla andra metodnamn ignoreras, även om vi uttryckligen ber om att köra dessa metoder.
Se några exempel på giltiga och ogiltiga pytest-filnamn.
test_login.py - valid login_test.py - valid testlogin.py -invalid logintest.py -invalid
Obs: Ja, vi kan uttryckligen be pytest att välja testlogin.py och logintest.py.
Se några exempel på giltiga och ogiltiga pytest-testmetoder.
def test_file1_method1(): - valid def testfile1_method1(): - valid def file1_method1(): - invalid
Obs: Även om vi uttryckligen nämner file1_method1(), kommer pytest inte att köra den här metoden.
Kör flera tester från en specifik fil och flera filer
För närvarande, inuti mappen study_pytest, har vi filen test_sample1.py. Anta att vi har flera filer, säg test_sample2.py och test_sample3.py. För att köra alla tester från alla filer i mappen och undermapparna behöver vi bara köra kommandot pytest.
py.test
Detta kör alla filnamn som börjar med test_ och filnamn som slutar med _test i den mappen och undermapparna under den mappen.
För att köra tester endast från en specifik fil kan vi använda py.test .
py.test test_sample1.py
Kör en delmängd av hela testet med Pytest
Ibland vill vi inte köra hela testsviten. Pytest låter oss köra specifika tester. Vi kan göra det på två sätt:
- Grouping av testnamn genom delsträngsmatchning.
- Grouping av tester av markörer.
Vi har redan test_sample1.py. Skapa en fil test_sample2.py och lägg till koden nedan i 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öljande just nu.
• test_sample1.py • test_file1_method1() • test_file1_method2() • test_sample2.py • test_file2_method1() • test_file2_method2()
Alternativ 1) Kör tester genom att matcha delsträngar
Här, för att köra alla tester som har metod1 i sitt namn, måste vi köra kommandot nedan.
py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity
Så om du kör py.test -k method1 -v får du följande 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 ===============================
Här kan man se, mot slutet, 2 tester avmarkerade av '-kmethod1', vilka är test_file1_metod2 och test_file2_metod2.
Prova att springa med olika kombinationer som följande.
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'
Alternativ 2) Kör tester med markörer
Pytest låter oss ställa in olika attribut för testmetoderna med hjälp av pytest-markörer, @pytest.mark. För att använda markörer i testfilen måste vi importera pytest till testfilerna. Här kommer vi att tillämpa olika markörnamn på testmetoderna och köra specifika tester baserat på markörnamnen. Vi kan definiera markörerna för varje testnamn genom att använda följande.
@pytest.mark.<name>.
Vi definierar markörerna set1 och set2 på testmetoderna, och vi kommer att köra testet med markörnamnen. Uppdatera testfilerna med följande kod.
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öra det markerade testet med kommandot nedan.
py.test -m <name> -m <name> mentions the marker name
Kör py.test -m set1. Detta kommer att köra metoderna test_file1_method1, test_file2_method1 och test_file2_method2. Om du kör py.test -m set2 körs test_file1_method2.
Kör tester parallellt med Pytest
Vanligtvis har en testsvit flera testfiler och hundratals testmetoder, vilket tar avsevärd tid att köra. Pytest låter oss köra tester parallellt. För det måste vi först installera pytest-xdist genom att köra kommandot nedan.
pip install pytest-xdist
Du kan köra testerna nu med följande kommando.
py.test -n 4
Här, -n kör testerna med flera arbetare. I kommandot ovan kommer det att finnas fyra arbetare som kör testerna.
Pytest-fixturer
Fixturer används när vi vill köra kod före varje testmetod. Så istället för att upprepa samma kod i varje test definierar vi fixturer. Vanligtvis används fixturer för att initiera databasanslutningar, skicka basen URL, och så vidare. En metod markeras som en pytest-fixtur genom att markera den med följande.
@pytest.fixture
En testmetod kan använda en pytest-fixtur genom att ange fixturen som en indataparameter. Skapa en ny fil test_basic_fixture.py med följande kod.
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"
Här:
- Vi har en fixtur med namnet supply_AA_BB_CC. Den här metoden returnerar en lista med tre värden.
- Vi har tre testmetoder som jämför mot vart och ett av värdena.
Varje testfunktion har ett inmatningsargument vars namn matchar en tillgänglig fixtur. Pytest anropar sedan motsvarande fixturmetod, och de returnerade värdena lagras i inmatningsargumentet, här listan [25,35,45]. Listobjekten används sedan i testmetoderna för jämförelsen. Kör nu testet och 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 =================================
Testet test_comparewithBB godkänns, eftersom zz=BB=35, och de återstående två testerna misslyckas.
Fixture-metoden har ett omfång endast inom testfilen där den är definierad. Om vi försöker komma åt fixture-metoden i någon annan testfil får vi ett felmeddelande som säger fixture. 'supply_AA_BB_CC' hittades inte för testmetoderna i de andra filerna.
För att använda samma fixtur mot flera testfiler skapar vi fixturmetoder i en fil som heter conftest.py. Låt oss se detta med pytest-exemplet nedan. Skapa tre filer, conftest.py, test_basic_fixture.py och test_basic_fixture2.py, med följande kod.
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 letar först efter fixturen i testfilen, och om den inte hittas letar den i conftest.py. Kör testet med py.test -k test_comparewith -v för att få resultatet nedan.
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 Parameteriserat test
Syftet med att parametrisera ett test är att köra ett test mot flera uppsättningar argument. Vi kan göra detta med @pytest.mark.parametrize. Vi kommer att se detta med pytest-exemplet nedan. Här skickar vi tre argument till en testmetod. Denna testmetod lägger till de två första argumenten och jämför resultatet med det tredje argumentet. Skapa testfilen test_addition.py med koden nedan.
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"
Här accepterar testmetoden tre argument: input1, input2 och output. Den adderar input1 och input2 och jämför summan med output. Låt oss köra testet med py.test -k test_add -v och 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 att testerna kördes två gånger, en kontrollerade 5+5 ==10 och den andra kontrollerade 3+5 ==12.
test_addition.py::test_add[5-5-10] godkänd
test_addition.py::test_add[3-5-12] MISSLYCKADES
Pytest Xfail och Skip Tests
Det kommer att finnas vissa situationer där vi inte vill utföra ett test, eller en testfall är inte relevant vid en viss tidpunkt. I dessa situationer har vi möjlighet att xfaila på testet eller hoppa över testerna. Det xfailade testet kommer att köras, men det kommer inte att räknas som en del av de underkända eller godkända testerna. Det kommer inte att finnas någon traceback visas om testet misslyckas. Vi kan xfail-tester med @pytest.mark.xfail. Hoppa överping Ett test innebär att testet inte kommer att köras. Vi kan hoppa över tester med @pytest.mark.skip. Redigera test_addition.py med koden nedan.
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"
Här:
- test_add_1 och test_add_2 hoppas över och kommer inte att köras.
- test_add_3 och test_add_4 är xfailed. Dessa tester kommer att köras och kommer att ingå i xfailed-testerna (vid testfel) eller xpassed-testerna (vid testgodkännande). Det kommer inte att finnas några tracåterbetalning för misslyckanden.
- test_add_5 och test_add_6 kommer att köras, och test_add_6 kommer att rapportera fel med en traceback medan test_add_5 klarar.
Kör testet med py.test test_addition.py -v och 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 =================
Resultat i XML-format
Vi kan skapa testresultat i XML-format, som vi kan mata in till Continuous Integration-servrar för vidare bearbetning. Detta kan göras med kommandot py.test test_sample1.py -v –junitxml=”result.xml”. result.xml kommer att registrera testkörningsresultatet. Hitta ett exempel på result.xml nedan.
<?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>
Från Vi kan se totalt två tester, varav ett har misslyckats. Nedanför kan du se detaljerna om varje utfört test under märka.
Pytest-ramverket för att testa ett API
Nu ska vi skapa ett litet pytest-ramverk för att testa ett API. API:et som används här är ett gratis API från reqres.inDen här webbplatsen tillhandahåller endast ett testbart API och lagrar inte våra data. Här kommer vi att skriva några tester för följande:
- Listar några användare.
- Logga in med användare.
Skapa filerna nedan med den angivna koden. Först har conftest.py en fixtur som kommer att tillhandahålla basen URL för alla testmetoder.
import pytest @pytest.fixture def supply_url(): return "https://reqres.in/api"
Därefter innehåller test_list_user.py testmetoderna för att lista giltiga och ogiltiga användare.
- test_list_valid_user testar efter en giltig användare och verifierar svaret.
- test_list_invaliduser testar efter en ogiltig användare och verifierar 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
Sedan innehåller test_login_user.py testmetoderna för att testa inloggningsfunktionen.
- test_login_valid testar det giltiga inloggningsförsöket med en e-postadress och ett lösenord.
- test_login_no_password testar det ogiltiga inloggningsförsöket utan att ange ett lösenord.
- test_login_no_email testar det ogiltiga inloggningsförsöket utan att skicka en e-postadress.
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 testet med py.test -v och se resultatet enligt följande.
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
Uppdatera testerna och testa olika utdata.





