PyTest-handleiding: Wat is PyTest? (met voorbeelden)
ā” Slimme samenvatting
Pytest is een Python Een testframework waarmee je eenvoudige, schaalbare tests kunt schrijven voor databases, API's en gebruikersinterfaces. Het ondersteunt fixtures, parameterisering, markers, parallelle uitvoeringen en gedetailleerde rapportage van beweringen, van basis unit tests tot complexe functionele tests.
Pytest is een van de meest gebruikte testframeworks in de Python ecosysteem. Het houdt testcode kort en leesbaar, vindt automatisch tests en groeit van een eerste bewering uit tot een complete testsuite voor databases, API's en gebruikersinterfaces. De onderstaande secties behandelen installatie, beweringen, fixtures, markers, parameterisering en parallelle uitvoeringen met praktische voorbeelden.
Wat is Pytest?
Pytest is een testframework waarmee je testcode kunt schrijven met behulp van de Python Een programmeertaal. Het helpt je bij het schrijven van eenvoudige en schaalbare testcases voor databases, API's en zelfs gebruikersinterfaces. Pytest wordt voornamelijk gebruikt voor het schrijven van tests voor API's en is schaalbaar van eenvoudige unit-tests tot complexe functionele tests.
Waarom Pytest gebruiken?
Enkele voordelen van pytest zijn:
- Heel gemakkelijk om mee te beginnen, vanwege de simpele en overzichtelijke syntaxis.
- Kan tests parallel uitvoeren.
- Je kunt een specifieke test of een subset van tests uitvoeren.
- Detecteert automatisch tests.
- Je kunt toetsen overslaan.
- Open source.
Hoe installeer ik Pytest?
Hieronder volgt de procedure voor het installeren van pytest:
Stap 1) Je kunt pytest installeren met het onderstaande commando.
pip install pytest==2.9.1
Zodra de installatie is voltooid, kunt u dit bevestigen met het volgende commando.
py.test -h
Dit toont de helpfunctie.
Eerste basisvoorbeeld van Pytest
Nu leer je hoe je pytest gebruikt aan de hand van een eenvoudig pytest-voorbeeld.
Maak een map genaamd study_pytest. In deze map ga je de testbestanden aanmaken. Navigeer via de commandoregel naar deze map. Maak in de map een bestand aan met de naam test_sample1.py.
Voeg de onderstaande code toe en sla het bestand op.
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"
Voer de test uit met behulp van de onderstaande opdracht.
py.test
Je krijgt de volgende uitvoer.
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
Hier, in test_sample1.py F., staat de letter F voor mislukking en de punt (.) voor succes. In het gedeelte met mislukkingen zie je de mislukte methode(s) en de regel waar de fout optreedt. Hier betekent x==y dat 5==6, wat onjuist is.
Vervolgens leer je meer over beweringen (assertions) in pytest.
Asserties in Pytest
Pytest-asserties zijn controles die een True- of False-status retourneren. In pytest wordt de uitvoering van een testmethode gestopt als een assertie mislukt. De resterende code in die testmethode wordt niet uitgevoerd en pytest gaat verder met de volgende testmethode.
Pytest assert-voorbeelden:
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.
Denk eens aan het volgende.
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Plaats deze code in test_file1_method1() in plaats van de onderstaande bewering.
assert x == y,"test failed"
Als u de test uitvoert, wordt de fout as AssertionError: test mislukt x=5 y=6.
Hoe Pytest de testbestanden en testmethoden identificeert
Standaard identificeert pytest alleen bestandsnamen die beginnen met toets_ of eindigend met _toets als de testbestanden. We kunnen echter ook expliciet andere bestandsnamen vermelden (later uitgelegd). Pytest vereist dat de namen van de testmethoden beginnen met proefAlle andere methodenamen worden genegeerd, zelfs als we expliciet vragen om die methoden uit te voeren.
Bekijk enkele voorbeelden van geldige en ongeldige pytest-bestandsnamen.
test_login.py - valid login_test.py - valid testlogin.py -invalid logintest.py -invalid
Opmerking: Ja, we kunnen pytest expliciet vragen om testlogin.py en logintest.py te selecteren.
Bekijk enkele voorbeelden van geldige en ongeldige pytest-testmethoden.
def test_file1_method1(): - valid def testfile1_method1(): - valid def file1_method1(): - invalid
Let op: zelfs als we expliciet file1_method1() vermelden, zal pytest deze methode niet uitvoeren.
Voer meerdere tests uit vanuit een specifiek bestand en meerdere bestanden
Momenteel bevindt zich in de map study_pytest het bestand test_sample1.py. Stel dat we meerdere bestanden hebben, bijvoorbeeld test_sample2.py en test_sample3.py. Om alle tests uit alle bestanden in de map en submappen uit te voeren, hoeven we alleen maar het commando pytest te gebruiken.
py.test
Dit zal alle bestanden uitvoeren die beginnen met test_ en eindigen op _test in die map en de submappen daaronder.
Om tests alleen vanuit een specifiek bestand uit te voeren, kunnen we py.test gebruiken. .
py.test test_sample1.py
Voer een deel van de volledige test uit met Pytest.
Soms willen we niet de hele testsuite uitvoeren. Met Pytest kunnen we specifieke tests uitvoeren. Dat kan op twee manieren:
- Grouping van testnamen door middel van substring-matching.
- Grouping van tests door middel van markers.
We hebben al een bestand test_sample1.py. Maak een bestand test_sample2.py aan en voeg de onderstaande code eraan toe.
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"
Momenteel hebben we het volgende.
⢠test_sample1.py ⢠test_file1_method1() ⢠test_file1_method2() ⢠test_sample2.py ⢠test_file2_method1() ⢠test_file2_method2()
Optie 1) Tests uitvoeren door middel van subtekenreeksvergelijking
Om hier alle tests uit te voeren die 'method1' in hun naam hebben, moeten we de onderstaande opdracht uitvoeren.
py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity
Als je py.test -k method1 -v uitvoert, krijg je het volgende resultaat.
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 ===============================
Hier zie je, tegen het einde, 2 tests gedeselecteerd door '-kmethod1', namelijk test_file1_method2 en test_file2_method2.
Probeer verschillende combinaties uit, zoals de volgende.
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'
Optie 2) Voer tests uit met markers
Met Pytest kunnen we verschillende attributen voor testmethoden instellen met behulp van pytest-markers, `@pytest.mark`. Om markers in het testbestand te gebruiken, moeten we pytest importeren. Hier zullen we verschillende markernamen aan de testmethoden toekennen en specifieke tests uitvoeren op basis van de markernamen. We kunnen de markers voor elke testnaam definiƫren met behulp van de volgende code.
@pytest.mark.<name>.
We definiƫren markers set1 en set2 voor de testmethoden en we zullen de test uitvoeren met behulp van deze markernamen. Werk de testbestanden bij met de volgende code.
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"
We kunnen de gemarkeerde test uitvoeren met het onderstaande commando.
py.test -m <name> -m <name> mentions the marker name
Voer `py.test -m set1` uit. Hiermee worden de methoden `test_file1_method1`, `test_file2_method1` en `test_file2_method2` uitgevoerd. Met `py.test -m set2` wordt `test_file1_method2` uitgevoerd.
Tests parallel uitvoeren met Pytest
Een testsuite bevat doorgaans meerdere testbestanden en honderden testmethoden, wat een aanzienlijke hoeveelheid tijd in beslag neemt. Pytest stelt ons in staat om tests parallel uit te voeren. Hiervoor moeten we eerst pytest-xdist installeren met behulp van de onderstaande opdracht.
pip install pytest-xdist
Je kunt de tests nu uitvoeren met het volgende commando.
py.test -n 4
Hier, -n De tests worden uitgevoerd met behulp van meerdere workers. In het bovenstaande commando worden vier workers ingezet om de tests uit te voeren.
Pytest-armaturen
Fixtures worden gebruikt wanneer we bepaalde code willen uitvoeren vóór elke testmethode. In plaats van dezelfde code in elke test te herhalen, definiëren we fixtures. Meestal worden fixtures gebruikt om databaseverbindingen te initialiseren en de basisfunctionaliteit door te geven. URL, enzovoort. Een methode wordt gemarkeerd als een pytest-fixture door deze te markeren met het volgende.
@pytest.fixture
Een testmethode kan een pytest-fixture gebruiken door de fixture als invoerparameter te specificeren. Maak een nieuw bestand test_basic_fixture.py aan met de volgende code.
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"
Hier:
- We hebben een fixture met de naam supply_AA_BB_CC. Deze methode retourneert een lijst met drie waarden.
- We hebben drie testmethoden die de waarden met elkaar vergelijken.
Elke testfunctie heeft een invoerargument waarvan de naam overeenkomt met een beschikbare fixture. Pytest roept vervolgens de bijbehorende fixture-methode aan en de geretourneerde waarden worden opgeslagen in het invoerargument, in dit geval de lijst [25,35,45]. De items in de lijst worden vervolgens in de testmethoden gebruikt voor de vergelijking. Voer nu de test uit en bekijk het resultaat.
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 =================================
De test test_comparewithBB slaagt, omdat zz=BB=35, en de overige twee tests falen.
De fixture-methode heeft alleen een bereik binnen het testbestand waarin deze is gedefinieerd. Als we proberen de fixture in een ander testbestand te benaderen, krijgen we een foutmelding met de tekst "fixture". 'supply_AA_BB_CC' niet gevonden voor de testmethoden in de andere bestanden.
Om dezelfde fixture te gebruiken voor meerdere testbestanden, maken we fixture-methoden aan in een bestand genaamd conftest.py. Laten we dit bekijken aan de hand van het onderstaande pytest-voorbeeld. Maak drie bestanden aan, conftest.py, test_basic_fixture.py en test_basic_fixture2.py, met de volgende code.
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 zoekt eerst naar de fixture in het testbestand, en als deze niet wordt gevonden, zoekt het in conftest.py. Voer de test uit met py.test -k test_comparewith -v om het onderstaande resultaat te krijgen.
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 geparametriseerde test
Het doel van het parameteriseren van een test is om een āātest uit te voeren met meerdere sets argumenten. Dit kunnen we doen met `@pytest.mark.parametrize`. We zullen dit zien aan de hand van het onderstaande pytest-voorbeeld. Hier geven we drie argumenten door aan een testmethode. Deze testmethode telt de eerste twee argumenten bij elkaar op en vergelijkt het resultaat met het derde argument. Maak het testbestand `test_addition.py` aan met de onderstaande code.
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"
De testmethode accepteert hier drie argumenten: input1, input2 en output. De methode telt input1 en input2 bij elkaar op en vergelijkt de som met de output. Laten we de test uitvoeren met py.test -k test_add -v en het resultaat bekijken.
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
Je kunt zien dat de tests twee keer zijn uitgevoerd: de ene keer werd gecontroleerd of 5+5 ==10 en de andere keer of 3+5 ==12.
test_addition.py::test_add[5-5-10] GESLAAGD
test_addition.py::test_add[3-5-12] MISLUKT
Pytest Xfail en Skip Tests
Er zullen zich situaties voordoen waarin we een test niet willen uitvoeren, of een testcase is op een bepaald moment niet relevant. In die situaties hebben we de mogelijkheid om de test te laten mislukken (xfail) of de tests over te slaan. De test die wordt afgekeurd, zal wel worden uitgevoerd, maar telt niet mee voor de geslaagde of mislukte tests. Er zal geen traceback wordt weergegeven als die test mislukt. We kunnen tests markeren als xfail met behulp van @pytest.mark.xfail. Overslaanping Een test betekent dat de test niet wordt uitgevoerd. We kunnen tests overslaan met `@pytest.mark.skip`. Bewerk het bestand `test_addition.py` met de onderstaande code.
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"
Hier:
- test_add_1 en test_add_2 worden overgeslagen en worden niet uitgevoerd.
- test_add_3 en test_add_4 zijn xfailed. Deze tests zullen worden uitgevoerd en zullen deel uitmaken van de xfailed (bij een testfout) of xpassed (bij een testpass) tests. Er zal geen sprake zijn van traceback voor mislukkingen.
- test_add_5 en test_add_6 worden uitgevoerd, en test_add_6 zal een foutmelding geven met een traceback terwijl test_add_5 slaagt.
Voer de test uit met py.test test_addition.py -v en bekijk het resultaat.
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 =================
Resultaten in XML-formaat
We kunnen testresultaten in XML-formaat genereren, die we vervolgens kunnen doorgeven aan Continuous Integration-servers voor verdere verwerking. Dit kan met het commando `py.test test_sample1.py -v ājunitxml=āresult.xmlā`. Het bestand `result.xml` bevat het resultaat van de testuitvoering. Hieronder vindt u een voorbeeld van een `result.xml`.
<?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>
Van We zien in totaal twee tests, waarvan er ƩƩn is mislukt. Daaronder vindt u de details van elke uitgevoerde test. label.
Pytest-framework voor het testen van een API
Nu gaan we een klein pytest-framework maken om een āāAPI te testen. De API die we hier gebruiken is een gratis API van eqres.inDeze website biedt uitsluitend een testbare API en slaat onze gegevens niet op. Hier schrijven we enkele tests voor het volgende:
- Een lijst met enkele gebruikers.
- Inloggen met gebruikersgegevens.
Maak de onderstaande bestanden aan met de gegeven code. Ten eerste bevat conftest.py een fixture die de basis zal leveren. URL voor alle testmethoden.
import pytest @pytest.fixture def supply_url(): return "https://reqres.in/api"
Vervolgens bevat test_list_user.py de testmethoden voor het weergeven van geldige en ongeldige gebruikers.
- test_list_valid_user controleert of een gebruiker geldig is en verifieert het antwoord.
- test_list_invaliduser test op een ongeldige gebruikersaanvraag en verifieert het antwoord.
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
Vervolgens bevat test_login_user.py de testmethoden voor het testen van de inlogfunctionaliteit.
- test_login_valid test de geldigheid van de inlogpoging met een e-mailadres en wachtwoord.
- test_login_no_password test de ongeldige inlogpoging zonder wachtwoord.
- test_login_no_email test de ongeldige inlogpoging zonder e-mailadres.
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
Voer de test uit met py.test -v en bekijk het resultaat zoals hieronder weergegeven.
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
Werk de tests bij en probeer verschillende uitvoerwaarden uit.





