PyTest-zelfstudie: wat is, hoe te installeren, raamwerk, beweringen
Wat is PyTest?
PyTest is een testframework waarmee gebruikers testcodes kunnen schrijven met behulp van Python programmeertaal. Het helpt u om eenvoudige en schaalbare testcases te schrijven voor databases, API's of UI. PyTest wordt voornamelijk gebruikt voor het schrijven van tests voor API's. Het helpt om tests te schrijven van eenvoudige unit tests tot complexe functionele tests.
Waarom PyTest gebruiken?
Enkele voordelen van pytest zijn
- Zeer gemakkelijk om mee te beginnen vanwege de eenvoudige en gemakkelijke syntaxis.
- Kan tests parallel uitvoeren.
- Kan een specifieke test of een subset van tests uitvoeren
- Tests automatisch detecteren
- Tests overslaan
- Open source
Hoe PyTest te installeren
Hieronder volgt een proces voor het installeren van PyTest:
Stap 1) U kunt pytest installeren via
pip install pytest==2.9.1
Zodra de installatie is voltooid, kunt u deze bevestigen met by
py.test -h
Hierdoor wordt de hulp weergegeven
Eerste basis-PyTest
Nu zullen we leren hoe we Pytest kunnen gebruiken met een eenvoudig PyTest-voorbeeld.
Maak een map study_pytest. We gaan onze testbestanden in deze map maken.
Navigeer naar die map op uw opdrachtregel.
Maak een bestand met de naam test_sample1.py in de map
Voeg de onderstaande code eraan toe en sla 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 opdracht
py.test
U krijgt uitvoer als
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.
F zegt mislukking
Punt(.) zegt succes.
In het gedeelte met fouten kunt u de mislukte methode(s) en de foutlijn zien. Hier betekent x==y 5==6, wat onwaar is.
Vervolgens zullen we in deze PyTest-tutorial leren over beweringen in PyTest.
Beweringen in PyTest
Pytest-beweringen zijn controles die de status True of False retourneren. In Python Pytest: als een bewering mislukt in een testmethode, wordt de uitvoering van die methode daar gestopt. De resterende code in die testmethode wordt niet uitgevoerd en Pytest-beweringen gaan door 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.
Overwegen
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Plaats deze code in test_file1_method1() in plaats van de 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 de bestandsnamen die beginnen met toets_ of eindigend met _toets als de testbestanden. We kunnen echter expliciet andere bestandsnamen vermelden (wordt later uitgelegd). Pytest vereist dat de namen van de testmethoden beginnen met "test.” Alle 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 kiezen
Bekijk enkele voorbeelden van geldige en ongeldige pytest-testmethoden
def test_file1_method1(): - valid def testfile1_method1(): - valid def file1_method1(): - invalid
Opmerking: 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 hebben we in de map study_pytest een bestand test_sample1.py. Stel dat we meerdere bestanden hebben, bijvoorbeeld test_sample2.py , test_sample3.py. Om alle tests uit te voeren vanuit alle bestanden in de map en submappen, hoeven we alleen maar de opdracht pytest uit te voeren.
py.test
Hierdoor worden alle bestandsnamen uitgevoerd die beginnen met test_ en de bestandsnamen die eindigen op _test in die map en submappen onder die map.
Om tests alleen vanuit een specifiek bestand uit te voeren, kunnen we py.test gebruiken
py.test test_sample1.py
Voer een subset van de volledige test uit met PyTest
Soms willen we niet de hele testsuite uitvoeren. Met Pytest kunnen we specifieke tests uitvoeren. We kunnen het op 2 manieren doen
- Groepering van testnamen op basis van substringmatching
- Groepering van tests op markers
We hebben al test_sample1.py. Maak een bestand test_sample2.py 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"
Dus dat hebben we momenteel
• test_sample1.py • test_file1_method1() • test_file1_method2() • test_sample2.py • test_file2_method1() • test_file2_method2()
Optie 1) Voer tests uit door middel van substringmatching
Hier moeten we alle tests uitvoeren met methode1 in de naam
py.test -k method1 -v -k <expression> is used to represent the substring to match -v increases the verbosity
Als u dus py.test -k method1 -v uitvoert, krijgt u 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 kun je het einde zien 2 tests gedeselecteerd door '-kmethod1' dit zijn test_file1_method2 en test_file2_method2
Probeer te rennen met verschillende combinaties, zoals: -
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 de testmethoden instellen met behulp van pytest-markers, @pytest.mark . Om markeringen in het testbestand te gebruiken, moeten we pytest in de testbestanden importeren.
Hier zullen we verschillende markernamen toepassen op testmethoden en specifieke tests uitvoeren op basis van markernamen. We kunnen de markeringen op elke testnaam definiëren met behulp van
@pytest.mark.<name>.
We definiëren markers set1 en set2 op de testmethoden en we zullen de test uitvoeren met behulp van de 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 door
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, test_file2_method2 uitgevoerd.
Als u py.test -m set2 uitvoert, wordt test_file1_method2 uitgevoerd.
Voer tests parallel uit met Pytest
Normaal gesproken zal een testpakket meerdere testbestanden en honderden testmethoden bevatten, wat een aanzienlijke hoeveelheid tijd zal vergen om uit te voeren. Met Pytest kunnen we tests parallel uitvoeren.
Daarvoor moeten we eerst pytest-xdist installeren door het uit te voeren
pip install pytest-xdist
U kunt nu tests uitvoeren door
py.test -n 4
-N voert de tests uit met behulp van meerdere werknemers. In de bovenstaande opdracht zijn er 4 werknemers om de test uit te voeren.
Pytest-armaturen
Fixtures worden gebruikt als we vóór elke testmethode code willen uitvoeren. Dus in plaats van bij elke test dezelfde code te herhalen, definiëren we armaturen. Meestal worden armaturen gebruikt om databaseverbindingen te initialiseren, de basis door te geven, enz
Een methode wordt gemarkeerd als een Pytest-fixture door te markeren met
@pytest.fixture
Een testmethode kan een Pytest-fixture gebruiken door de armatuur als invoerparameter te vermelden.
Maak een nieuw bestand test_basic_fixture.py 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"
Here
- We hebben een armatuur met de naam supply_AA_BB_CC. Deze methode retourneert een lijst met 3 waarden.
- We hebben 3 testmethoden die vergelijken met elk van de waarden.
Elk van de testfuncties heeft een invoerargument waarvan de naam overeenkomt met een beschikbaar armatuur. Pytest roept vervolgens de overeenkomstige armatuurmethode aan en de geretourneerde waarden worden opgeslagen in het invoerargument , hier de lijst [25,35,45]. Nu worden de lijstitems gebruikt in testmethoden 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 is geslaagd omdat zz=BB=35, en de overige 2 tests zijn mislukt.
De opspanmethode heeft alleen een bereik binnen het testbestand waarin het is gedefinieerd. Als we proberen toegang te krijgen tot het armatuur in een ander testbestand, krijgen we een foutmelding met de melding armatuur 'supply_AA_BB_CC' niet gevonden voor de testmethoden in andere bestanden.
Om hetzelfde armatuur tegen meerdere testbestanden te gebruiken, zullen we armatuurmethoden maken in een bestand met de naam conftest.py.
Laten we dit bekijken met het onderstaande PyTest-voorbeeld. Maak 3 bestanden conftest.py, test_basic_fixture.py, 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 zal eerst naar het armatuur in het testbestand zoeken en als het niet wordt gevonden, zal het in conftest.py zoeken
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 parametriseren van een test is om een test uit te voeren op basis van meerdere sets argumenten. We kunnen dit doen via @pytest.mark.parametrize.
We zullen dit zien met het onderstaande PyTest-voorbeeld. Hier zullen we 3 argumenten doorgeven aan een testmethode. Deze testmethode telt de eerste twee argumenten op en vergelijkt deze met het derde argument.
Maak het testbestand test_addition.py 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"
Hier accepteert de testmethode 3 argumenten: input1, input2, output. Het voegt input1 en input2 toe en vergelijkt 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 2 keer zijn uitgevoerd: één controle 5+5 ==10 en andere controle 3+5 ==12
test_addition.py::test_add[5-5-10] GESLAAGD
test_addition.py::test_add[3-5-12] MISLUKT
Pytest Xfail / Tests overslaan
Er zullen situaties zijn waarin we een test niet willen uitvoeren, of a testcase is niet relevant voor een bepaalde tijd. In die situaties hebben we de mogelijkheid om de test Xfail te geven of de tests over te slaan
De xfailed test wordt uitgevoerd, maar telt niet mee als deel mislukte of geslaagde tests. Er wordt geen traceback weergegeven als die test mislukt. We kunnen xfailed tests gebruiken
@pytest.mark.xfail.
Het overslaan van een test betekent dat de test niet wordt uitgevoerd. We kunnen tests overslaan met behulp van
@pytest.mark.skip.
Bewerk 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"
Here
- test_add_1 en test_add_2 worden overgeslagen en worden niet uitgevoerd.
- test_add_3 en test_add_4 zijn mislukt. Deze tests worden uitgevoerd en maken deel uit van xfailed (on test fail) of xpassed (on test pass) tests. Er is geen traceback voor storingen.
- test_add_5 en test_add_6 worden uitgevoerd en test_add_6 rapporteert een fout met 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-XML
We kunnen testresultaten in XML-formaat creëren die we naar Continuous Integration-servers kunnen sturen voor verdere verwerking enzovoort. Dit kan gedaan worden door
py.test test_sample1.py -v –junitxml=”resultaat.xml”
De result.xml registreert het resultaat van de testuitvoering. Hieronder vindt u een voorbeeld 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 kunnen in totaal twee tests zien waarvan er één mislukt is. Daaronder ziet u de details van elke uitgevoerde test onder label.
Pytest Framework Een API testen
Nu gaan we een klein pytest-framework maken om een API te testen. De hier gebruikte API is gratis https://reqres.in/. Deze website is alleen bedoeld om een testbare API te bieden. Deze website bewaart onze gegevens niet.
Hier zullen we enkele tests voor schrijven
- enkele gebruikers vermelden
- inloggen met gebruikers
Maak de onderstaande bestanden met de gegeven code
conftest.py – heb een armatuur die de basis-URL levert voor alle testmethoden
import pytest @pytest.fixture def supply_url(): return "https://reqres.in/api"
test_list_user.py – bevat de testmethoden voor het vermelden van geldige en ongeldige gebruikers
- test_list_valid_user test of de gebruiker geldig ophaalt en verifieert het antwoord
- test_list_invaliduser test op ongeldige gebruikersophaalacties 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
test_login_user.py – bevat testmethoden voor het testen van de inlogfunctionaliteit.
- test_login_valid test de geldige inlogpoging met e-mailadres en wachtwoord
- test_login_no_password test de ongeldige inlogpoging zonder het wachtwoord door te geven
- test_login_no_email test de ongeldige inlogpoging zonder e-mailadres door te geven.
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
Zie het resultaat als
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
Update de tests en probeer verschillende uitgangen
Samenvatting
In deze PyTest-tutorial hebben we het besproken
- Installeer pytest met behulp van pip installeren pytest=2.9.1
- Eenvoudig pytest-programma en voer het uit met de opdracht py.test.
- Beweringsverklaringen, beweren x==y, retourneren Waar of Onwaar.
- Hoe pytest testbestanden en -methoden identificeert.
- Testbestanden die beginnen met toets_ of eindigend met _toets
- Testmethoden beginnend met proef
- De opdracht py.test voert alle testbestanden in die map en submappen uit. Om een specifiek bestand uit te voeren, kunnen we de opdracht py.test gebruiken
- Voer een subset van testmethoden uit
- Groepering van testnamen op subtekenreeks matching.py.test -k -v zal alle tests uitvoeren op zijn naam.
- Voer een test uit met markers. Markeer de tests met @pytest.mark. en voer de tests uit met pytest -m om tests uit te voeren die zijn gemarkeerd als .
- Voer tests parallel uit
- Installeer pytest-xdist met pip install pytest-xdist
- Voer tests uit met py.test -n NUM waarbij NUM het aantal werkrollen is
- Fixatiemethoden maken om code vóór elke test uit te voeren door de methode te markeren met @pytest.fixture
- De reikwijdte van een opspanningsmethode ligt binnen het bestand waarin deze is gedefinieerd.
- Een armatuurmethode is toegankelijk via meerdere testbestanden door deze te definiëren in het bestand conftest.py.
- Een testmethode heeft toegang tot een Pytest-fixture door deze als invoerargument te gebruiken.
- Tests parametriseren om deze uit te voeren op meerdere sets invoer.
@pytest.mark.parametrize(“invoer1, invoer2, uitvoer”,[(5,5,10),(3,5,12)])
def test_add(invoer1, invoer2, uitvoer):
beweer input1+input2 == output,”mislukt”
zal de test uitvoeren met invoer (5,5,10) en (3,5,12) - Skip/xfail-tests met @pytets.mark.skip en @pytest.mark.xfail
- Maak testresultaten in XML-formaat die de uitgevoerde testdetails bevatten met behulp van py.test test_sample1.py -v –junitxml=”result.xml”
- Een voorbeeld van een pytest-framework om een API te testen