Tutorial su PyTest: Cos'è PyTest? (con esempi)
⚡ Riepilogo intelligente
Pytest è un Python Framework di test che ti aiuta a scrivere test semplici e scalabili per database, API e interfacce utente. Supporta fixture, parametrizzazione, marcatori, esecuzioni parallele e report dettagliati sulle asserzioni, dai test unitari di base ai test funzionali complessi.
Pytest è uno dei framework di test più utilizzati nel Python ecosistema. Mantiene il codice di test breve e leggibile, individua automaticamente i test e si evolve da una prima asserzione in una suite di test completa per database, API e interfacce utente. Le sezioni seguenti illustrano l'installazione, le asserzioni, i fixture, i marker, la parametrizzazione e le esecuzioni parallele con esempi pratici.
Cos'è Pytest?
Pytest è un framework di test che ti permette di scrivere codice di test utilizzando il Python Linguaggio di programmazione. Aiuta a scrivere casi di test semplici e scalabili per database, API e persino interfacce utente. Pytest viene utilizzato principalmente per scrivere test per le API e si adatta sia a semplici test unitari che a complessi test funzionali.
Perché utilizzare Pytest?
Alcuni dei vantaggi di pytest sono:
- È molto facile da usare fin da subito, grazie alla sua sintassi semplice e pulita.
- Può eseguire test in parallelo.
- È possibile eseguire un test specifico o un sottoinsieme di test.
- Rileva automaticamente i test.
- È possibile saltare i test.
- Sorgente aperta.
Come installare Pytest
Di seguito è riportata la procedura per installare pytest:
Passo 1) È possibile installare pytest con il comando seguente.
pip install pytest==2.9.1
Una volta completata l'installazione, è possibile confermarla con il seguente comando.
py.test -h
Verrà visualizzata la guida.
Primo esempio base di Pytest
Ora imparerai come usare pytest con un esempio di base.
Crea una cartella chiamata study_pytest. All'interno di questa cartella creerai i file di test. Accedi a questa cartella tramite la riga di comando. Crea un file chiamato test_sample1.py al suo interno.
Aggiungi il codice seguente e salva.
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"
Eseguire il test utilizzando il comando seguente.
py.test
Otterrai il seguente risultato.
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
Qui, in test_sample1.py F., la lettera F indica fallimento e il punto (.) indica successo. Nella sezione degli errori, è possibile visualizzare il/i metodo/i fallito/i e la riga dell'errore. Qui x==y significa 5==6, che è falso.
Successivamente, imparerai a conoscere le asserzioni in pytest.
Asserzioni in Pytest
Le asserzioni di Pytest sono controlli che restituiscono uno stato True o False. In Pytest, se un'asserzione fallisce in un metodo di test, l'esecuzione di quel metodo viene interrotta. Il codice rimanente in quel metodo di test non viene eseguito e Pytest continua con il metodo di test successivo.
Esempi di asserzioni Pytest:
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.
Considera quanto segue.
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Inserisci questo codice in test_file1_method1() al posto dell'asserzione seguente.
assert x == y,"test failed"
L'esecuzione del test restituirà l'errore come AssertionError: test fallito x=5 y=6.
Come Pytest identifica i file di test e i metodi di test
Per impostazione predefinita, pytest identifica solo i nomi dei file che iniziano con test_ o termina con _test come file di test. Possiamo però menzionare esplicitamente altri nomi di file (spiegato più avanti). Pytest richiede che i nomi dei metodi di test inizino con testTutti gli altri nomi di metodi vengono ignorati, anche se richiediamo esplicitamente di eseguirli.
Ecco alcuni esempi di nomi di file pytest validi e non validi.
test_login.py - valid login_test.py - valid testlogin.py -invalid logintest.py -invalid
Nota: Sì, possiamo chiedere esplicitamente a pytest di selezionare testlogin.py e logintest.py.
Ecco alcuni esempi di metodi di test pytest validi e non validi.
def test_file1_method1(): - valid def testfile1_method1(): - valid def file1_method1(): - invalid
Nota: anche se menzioniamo esplicitamente file1_method1(), pytest non eseguirà questo metodo.
Esegui più test da un file specifico e da più file
Attualmente, all'interno della cartella study_pytest, abbiamo un file chiamato test_sample1.py. Supponiamo di avere più file, ad esempio test_sample2.py e test_sample3.py. Per eseguire tutti i test da tutti i file presenti nella cartella e nelle sottocartelle, è sufficiente eseguire il comando pytest.
py.test
Questo eseguirà tutti i file i cui nomi iniziano con test_ e terminano con _test in quella cartella e nelle sottocartelle al suo interno.
Per eseguire i test solo da un file specifico, possiamo usare py.test .
py.test test_sample1.py
Esegui un sottoinsieme dell'intero test con Pytest
A volte non vogliamo eseguire l'intera suite di test. Pytest ci permette di eseguire test specifici. Possiamo farlo in due modi:
- Grouping dei nomi dei test tramite corrispondenza di sottostringhe.
- Grouping dei test mediante marcatori.
Abbiamo già il file test_sample1.py. Crea un file test_sample2.py e aggiungi il codice seguente al suo interno.
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"
Quindi al momento abbiamo quanto segue.
• test_sample1.py • test_file1_method1() • test_file1_method2() • test_sample2.py • test_file2_method1() • test_file2_method2()
Opzione 1) Eseguire i test tramite corrispondenza di sottostringhe
Qui, per eseguire tutti i test che contengono "method1" nel loro nome, dobbiamo eseguire il comando seguente.
py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity
Eseguendo py.test -k method1 -v si otterrà il seguente risultato.
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 ===============================
Qui puoi vedere, verso la fine, 2 test deselezionati da '-kmethod1', che sono test_file1_method2 e test_file2_method2.
Prova a eseguire varie combinazioni come le seguenti.
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'
Opzione 2) Eseguire i test in base ai marcatori
Pytest ci permette di impostare diversi attributi per i metodi di test utilizzando i marcatori di pytest, @pytest.mark. Per utilizzare i marcatori nel file di test, dobbiamo importare pytest nei file di test. Qui applicheremo diversi nomi di marcatori ai metodi di test ed eseguiremo test specifici in base ai nomi dei marcatori. Possiamo definire i marcatori su ciascun nome di test utilizzando il seguente codice.
@pytest.mark.<name>.
Stiamo definendo i marcatori set1 e set2 sui metodi di test ed eseguiremo il test utilizzando i nomi dei marcatori. Aggiorna i file di test con il seguente codice.
test_campione1.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_campione2.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"
Possiamo eseguire il test contrassegnato con il comando seguente.
py.test -m <name> -m <name> mentions the marker name
Esegui py.test -m set1. Questo eseguirà i metodi test_file1_method1, test_file2_method1 e test_file2_method2. Eseguendo py.test -m set2 verrà eseguito test_file1_method2.
Esegui i test in parallelo con Pytest
Solitamente, una suite di test contiene diversi file di test e centinaia di metodi di test, la cui esecuzione richiede un tempo considerevole. Pytest ci permette di eseguire i test in parallelo. Per farlo, dobbiamo prima installare pytest-xdist eseguendo il comando seguente.
pip install pytest-xdist
Ora puoi eseguire i test con il seguente comando.
py.test -n 4
Qui, -n esegue i test utilizzando più worker. Nel comando sopra riportato, verranno utilizzati quattro worker per eseguire i test.
Infissi Pytest
I fixture vengono utilizzati quando vogliamo eseguire del codice prima di ogni metodo di test. Quindi, invece di ripetere lo stesso codice in ogni test, definiamo dei fixture. Solitamente, i fixture vengono utilizzati per inizializzare le connessioni al database, passare la base URLe così via. Un metodo viene contrassegnato come fixture di pytest contrassegnandolo con quanto segue.
@pytest.fixture
Un metodo di test può utilizzare una fixture di pytest specificandola come parametro di input. Crea un nuovo file test_basic_fixture.py con il seguente codice.
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"
Qui:
- Abbiamo un componente denominato supply_AA_BB_CC. Questo metodo restituisce un elenco di tre valori.
- Abbiamo tre metodi di test che vengono confrontati con ciascuno dei valori.
Ciascuna delle funzioni di test ha un argomento di input il cui nome corrisponde a un fixture disponibile. Pytest richiama quindi il metodo fixture corrispondente e i valori restituiti vengono memorizzati nell'argomento di input, in questo caso la lista [25,35,45]. Gli elementi della lista vengono poi utilizzati nei metodi di test per il confronto. Ora esegui il test e osserva il risultato.
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 =================================
Il test test_comparewithBB ha esito positivo, poiché zz=BB=35, mentre i restanti due test falliscono.
Il metodo fixture ha un ambito limitato al file di test in cui è definito. Se proviamo ad accedere al fixture in un altro file di test, otterremo un errore che indica fixture "supply_AA_BB_CC" non trovato per i metodi di test negli altri file.
Per utilizzare la stessa fixture su più file di test, creiamo dei metodi fixture in un file chiamato conftest.py. Vediamo come fare con l'esempio di pytest riportato di seguito. Creiamo tre file, conftest.py, test_basic_fixture.py e test_basic_fixture2.py, con il codice seguente.
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 cercherà prima il fixture nel file di test e, se non lo trova, lo cercherà in conftest.py. Esegui il test con py.test -k test_comparewith -v per ottenere il risultato riportato di seguito.
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
Test parametrizzato Pytest
Lo scopo della parametrizzazione di un test è quello di eseguirlo con più set di argomenti. Possiamo farlo con `@pytest.mark.parametrize`. Vedremo questo con l'esempio di pytest riportato di seguito. Qui passeremo tre argomenti a un metodo di test. Questo metodo di test sommerà i primi due argomenti e confronterà il risultato con il terzo argomento. Crea il file di test `test_addition.py` con il codice seguente.
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"
Qui il metodo di test accetta tre argomenti: input1, input2 e output. Somma input1 e input2 e confronta la somma con output. Eseguiamo il test con py.test -k test_add -v e vediamo il risultato.
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
Come puoi vedere, i test sono stati eseguiti due volte: una verifica che 5+5 ==10 e l'altra che verifica che 3+5 ==12.
test_addition.py::test_add[5-5-10] SUPERATO
test_addition.py::test_add[3-5-12] NON FALLITO
Test Pytest Xfail e Skip
Ci saranno alcune situazioni in cui non vorremo eseguire un test o un caso di prova non è rilevante in un momento particolare. In tali situazioni, abbiamo la possibilità di contrassegnare come fallito il test o di saltare i test. Il test contrassegnato come fallito verrà eseguito, ma non verrà conteggiato come parte dei test falliti o superati. Non ci sarà alcun tracViene visualizzato un eback se il test fallisce. Possiamo contrassegnare i test come falliti utilizzando @pytest.mark.xfail. Saltaping Un test significa che il test non verrà eseguito. Possiamo saltare i test usando @pytest.mark.skip. Modifica il file test_addition.py con il codice seguente.
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"
Qui:
- test_add_1 e test_add_2 vengono saltati e non verranno eseguiti.
- test_add_3 e test_add_4 sono xfailed. Questi test verranno eseguiti e faranno parte dei test xfailed (in caso di errore del test) o xpassed (in caso di successo del test). Non ci sarà alcun traceBack per i guasti.
- test_add_5 e test_add_6 verranno eseguiti e test_add_6 segnalerà un errore con un traceback mentre test_add_5 va a buon fine.
Esegui il test con py.test test_addition.py -v e visualizza il risultato.
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 =================
Risultati in formato XML
Possiamo creare risultati dei test in formato XML, che possiamo poi inviare ai server di Continuous Integration per l'ulteriore elaborazione. Questo può essere fatto con il comando py.test test_sample1.py -v –junitxml=”result.xml”. Il file result.xml registrerà il risultato dell'esecuzione del test. Di seguito è riportato un esempio di 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>
Da possiamo vedere un totale di due test, di cui uno è fallito. Sotto di esso, puoi vedere i dettagli relativi a ciascun test eseguito sotto etichetta.
Framework Pytest per il test di un'API
Ora creeremo un piccolo framework pytest per testare un'API. L'API utilizzata qui è gratuita. reqres.inQuesto sito web fornisce semplicemente un'API testabile e non memorizza i nostri dati. Qui scriveremo alcuni test per i seguenti casi:
- Elenco di alcuni utenti.
- Accedi con gli utenti.
Crea i file seguenti con il codice fornito. Innanzitutto, conftest.py ha un fixture che fornirà la base URL per tutti i metodi di prova.
import pytest @pytest.fixture def supply_url(): return "https://reqres.in/api"
Successivamente, il file test_list_user.py contiene i metodi di test per elencare gli utenti validi e non validi.
- test_list_valid_user verifica la validità dell'utente recuperato e ne controlla la risposta.
- test_list_invaliduser verifica la presenza di un utente non valido nel recupero e controlla la risposta.
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
Il file test_login_user.py contiene quindi i metodi di test per verificare la funzionalità di accesso.
- La funzione test_login_valid verifica la validità del tentativo di accesso con email e password.
- La funzione test_login_no_password verifica il tentativo di accesso non valido senza l'inserimento di una password.
- test_login_no_email verifica il tentativo di accesso non valido senza passare un indirizzo email.
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
Esegui il test utilizzando py.test -v e osserva il risultato come segue.
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
Aggiorna i test e prova diversi output.





