PyTest Tutorial: Hvad er, hvordan man installerer, rammeværk, påstande

Hvad er PyTest?

PyTest er en testramme, der giver brugerne mulighed for at skrive testkoder vha Python programmeringssprog. Det hjælper dig med at skrive enkle og skalerbare testcases til databaser, API'er eller UI. PyTest bruges hovedsageligt til at skrive test til API'er. Det hjælper med at skrive test fra simple enhedstests til komplekse funktionelle tests.

Hvorfor bruge PyTest?

Nogle af fordelene ved pytest er

  • Meget let at starte med på grund af dens enkle og nemme syntaks.
  • Kan køre test parallelt.
  • Kan køre en specifik test eller en delmængde af tests
  • Registrer automatisk tests
  • Spring prøver over
  • Open source

Sådan installeres PyTest

Følgende er en proces til, hvordan du installerer PyTest:

Trin 1) Du kan installere pytest ved

pip install pytest==2.9.1

Når installationen er færdig, kan du bekræfte den med ved

py.test -h

Dette vil vise hjælpen

installer PyTest

Første grundlæggende PyTest

Nu vil vi lære, hvordan man bruger Pytest med et grundlæggende PyTest-eksempel.

Opret en mappe study_pytest. Vi vil oprette vores testfiler i denne mappe.

Venligst naviger til den mappe på din kommandolinje.

Opret en fil med navnet test_sample1.py inde i mappen

Første grundlæggende PyTest

Tilføj nedenstående kode til det 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

py.test

Du får output som

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

Første grundlæggende PyTest

Her i test_sample1.py F.

F siger fiasko

Prik(.) siger succes.

I fejlafsnittet kan du se de(n) mislykkede metode(r) og fejllinjen. Her betyder x==y 5==6, hvilket er falsk.

Næste i denne PyTest-tutorial lærer vi om påstand i PyTest.

Påstande i PyTest

Pytest-påstande er kontroller, der returnerer enten True eller False status. I Python Pytest, hvis en påstand mislykkes i en testmetode, stoppes udførelse af denne metode der. Den resterende kode i den testmetode udføres ikke, og Pytest-påstande fortsætter med den næste testmetode.

Pytest Assert eksempler:

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

assert x == y,"test failed because x=" + str(x) + " y=" + str(y)

Placer denne kode i test_file1_method1() i stedet for påstanden

assert x == y,"test failed"

At køre testen vil give fejlen som AssertionError: Test mislykkedes x=5 y=6

Hvordan PyTest identificerer testfilerne og testmetoderne

Som standard identificerer pytest kun filnavnene, der starter med prøve_ eller slutter med _prøve som testfilerne. Vi kan dog udtrykkeligt nævne andre filnavne (forklaret senere). Pytest kræver testmetodenavnene til at starte med "prøve." Alle andre metodenavne vil blive ignoreret, selvom vi udtrykkeligt 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 udtrykkeligt 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: Selvom vi udtrykkeligt nævner file1_method1() vil pytest ikke køre denne metode.

Kør flere tests fra en specifik fil og flere filer

I øjeblikket, inde i mappen study_pytest, har vi en fil test_sample1.py. Antag, at vi har flere filer, f.eks. test_sample2.py, test_sample3.py. For at køre alle testene fra alle filerne i mappen og undermapperne skal vi bare køre pytest-kommandoen.

py.test

Dette vil køre alle filnavne, der starter med test_ og filnavne, der slutter med _test i den mappe og undermapper under den mappe.

For kun at køre test fra en bestemt fil, kan vi bruge py.test

py.test test_sample1.py

Kør et undersæt af hele testen med PyTest

Nogle gange ønsker vi ikke at køre hele testpakken. Pytest giver os mulighed for at køre specifikke tests. Vi kan gøre det på 2 måder

  • Gruppering af testnavne efter understrengmatching
  • Gruppering af test efter markører

Vi har allerede test_sample1.py. Opret en fil test_sample2.py og tilføj nedenstående kode 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å det har vi pt

• test_sample1.py
• test_file1_method1()
• test_file1_method2()
• test_sample2.py
• test_file2_method1()
• test_file2_method2()

Mulighed 1) Kør test ved at matche understrenge

Her skal vi køre alle de test, der har metode1 i sit navn

py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity

Så at køre py.test -k method1 -v vil give dig 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_fil1_metode2 og test_fil2_metode2

Prøv at løbe med forskellige kombinationer som:-

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 på testfilerne.

Her vil vi anvende forskellige markørnavne på testmetoder og køre specifikke test baseret på markørnavne. Vi kan definere markørerne på hver testnavne ved at bruge

@pytest.mark.<name>.			

Vi definerer markører sæt1 og sæt2 på testmetoderne, og vi vil køre testen ved at bruge 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 ved

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, test_file2_method2.

At køre py.test -m set2 vil køre test_file1_method2.

Kør tests parallelt med Pytest

Normalt vil en testpakke have flere testfiler og hundredvis af testmetoder, som vil tage lang tid at udføre. Pytest giver os mulighed for at køre tests parallelt.

Til det skal vi først installere pytest-xdist ved at køre

pip install pytest-xdist

Kør tests parallelt med Pytest

Du kan køre test nu ved

py.test -n 4

-n kører testene ved at bruge flere arbejdere. I ovenstående kommando vil der være 4 arbejdere til at køre testen.

Pytest-armaturer

Fixtures bruges, når vi ønsker at køre noget kode før hver testmetode. Så i stedet for at gentage den samme kode i hver test, definerer vi armaturer. Normalt bruges fixtures til at initialisere databaseforbindelser, sende basen osv

En metode markeres som et Pytest-armatur ved at markere med

@pytest.fixture

En testmetode kan bruge en Pytest-fixtur ved at nævne fixturen 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 et armatur ved navn supply_AA_BB_CC. Denne metode vil returnere en liste med 3 værdier.
  • Vi har 3 testmetoder, der sammenligner med hver af værdierne.

Hver af testfunktionerne har et input-argument, hvis navn stemmer overens med en tilgængelig fixtur. Pytest kalder derefter den tilsvarende fixturmetode, og de returnerede værdier vil blive gemt i input-argumentet , her listen [25,35,45]. Nu bliver listepunkterne brugt i testmetoder 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 er bestået, da zz=BB=35, og de resterende 2 tests er mislykkedes.

Fixturmetoden har kun et omfang inden for den testfil, den er defineret. Hvis vi forsøger at få adgang til armaturet i en anden testfil, får vi en fejl, der siger fixture 'supply_AA_BB_CC' ikke fundet for testmetoderne i andre filer.

For at bruge den samme fixtur mod flere testfiler, vil vi oprette fixturmetoder i en fil kaldet conftest.py.

Lad os se dette ved nedenstående PyTest-eksempel. Opret 3 filer conftest.py, test_basic_fixture.py, 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 lede efter fixturen i testfilen først, og hvis den ikke findes, vil den søge i conftest.py

Kør testen med py.test -k test_comparewith -v for at få resultatet som 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 parametrere en test er at køre en test mod flere sæt argumenter. Vi kan gøre dette ved at @pytest.mark.parametrize.

Vi vil se dette med nedenstående PyTest-eksempel. Her vil vi videregive 3 argumenter til en testmetode. Denne testmetode vil tilføje de første 2 argumenter og sammenligne dem med det 3. 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 3 argumenter - input1, input2, output. Den tilføjer input1 og input2 og sammenligner 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 testene kørt 2 gange – den ene tjekker 5+5 ==10 og den anden tjekker 3+5 ==12

test_addition.py::test_add[5-5-10] BESTÅET

test_addition.py::test_add[3-5-12] MISLYKKES

Pytest Xfail / Spring over tests

Der vil være nogle situationer, hvor vi ikke ønsker at udføre en test, eller en test sag er ikke relevant for et bestemt tidspunkt. I disse situationer har vi mulighed for at Xfail testen eller springe testene over

Den xmislykkede test vil blive udført, men den tælles ikke som delbeståede eller beståede prøver. Der vises ingen sporing, hvis testen mislykkes. Vi kan xfejle test vha

@pytest.mark.xfail.

At springe en test over betyder, at testen ikke bliver udført. Vi kan springe prøver over vha

@pytest.mark.spring.

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 xfejlet. Disse tests vil blive udført og vil være en del af xmislykkede (ved testfejl) eller xbeståede (ved testbestået) test. Der vil ikke være nogen sporing for fejl.
  • test_add_5 og test_add_6 vil blive udført, og test_add_6 vil rapportere fejl med sporing, mens test_add_5 består

Udfør testen ved at 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 XML

Vi kan oprette testresultater i XML-format, som vi kan føre til kontinuerlig integrationsservere til videre behandling og så. Dette kan gøres ved

py.test test_sample1.py -v –junitxml="result.xml"

Resultatet.xml registrerer 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 er ikke bestået. Nedenfor kan du se detaljerne vedrørende hver udført test under tag.

Pytest Framework Test af en API

Nu vil vi lave en lille pytest-ramme til at teste en API. Den her anvendte API er en gratis fra https://reqres.in/. Denne hjemmeside er kun for at levere testbar API. Denne hjemmeside gemmer ikke vores data.

Her vil vi skrive nogle tests til

  • liste nogle brugere
  • log ind med brugere

Opret nedenstående filer med den angivne kode

conftest.py – har en armatur, der leverer basis-url til alle testmetoder

import pytest
@pytest.fixture
def supply_url():
	return "https://reqres.in/api"

test_list_user.py – indeholder testmetoderne til at angive gyldige og ugyldige brugere

  • test_list_valid_user tester for gyldig brugerhentning og verificerer svaret
  • test_list_invaliduser tester for 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

test_login_user.py – indeholder testmetoder til test af login-funktionalitet.

  • test_login_valid tester det gyldige loginforsøg med e-mail og adgangskode
  • test_login_no_password tester det ugyldige loginforsøg uden at sende adgangskode
  • test_login_no_email tester det ugyldige loginforsøg uden at sende 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 ved at bruge py.test -v

Se resultatet som

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 udgange

Resumé

I denne PyTest-tutorial dækkede vi

  • Installer pytest vha pip installation pytest=2.9.1
  • Simpelt pytest-program og kør det med kommandoen py.test.
  • Påstandsudsagn, assert x==y, vil returnere enten True eller False.
  • Hvordan pytest identificerer testfiler og metoder.
  • Test filer starter med prøve_ eller slutter med _prøve
  • Testmetoder startende med prøve
  • kommandoen py.test vil køre alle testfilerne i den mappe og undermapper. For at køre en bestemt fil kan vi bruge kommandoen py.test
  • Kør et undersæt af testmetoder
  • Gruppering af testnavne efter understreng matching.py.test -k -v vil køre alle de test, der har i dens navn.
  • Kør test efter markører. Marker testene med @pytest.mark. og kør testene ved hjælp af pytest -m at køre test markeret som .
  • Kør test parallelt
  • Installer pytest-xdist ved hjælp af pip install pytest-xdist
  • Kør test ved hjælp af py.test -n NUM hvor NUM er antallet af arbejdere
  • Oprettelse af fixturmetoder til at køre kode før hver test ved at markere metoden med @pytest.fixture
  • Omfanget af en fixturmetode er inden for den fil, den er defineret.
  • En fixturmetode kan tilgås på tværs af flere testfiler ved at definere den i filen conftest.py.
  • En testmetode kan få adgang til et Pytest-armatur ved at bruge det som et input-argument.
  • Parametriserende test for at køre det mod flere sæt input.
    @pytest.mark.parametrize(“input1, input2, output”,[(5,5,10),(3,5,12)])
    def test_add(input1, input2, output):
    hævde input1+input2 == output,"mislykkedes"
    vil køre testen med input (5,5,10) og (3,5,12)
  • Spring/xfail tests ved hjælp af @pytets.mark.skip og @pytest.mark.xfail
  • Opret testresultater i XML-format, som dækker udførte testdetaljer ved hjælp af py.test test_sample1.py -v –junitxml="result.xml"
  • Et eksempel på en pytest-ramme til at teste en API