Tutorial PyTest: cos'è, come installare, framework, asserzioni
Cos'è PyTest?
PyTest è un framework di test che consente agli utenti di scrivere codici di test utilizzando Python linguaggio di programmazione. Ti aiuta a scrivere casi di test semplici e scalabili per database, API o UI. PyTest è usato principalmente per scrivere test per API. Aiuta a scrivere test da semplici test unitari a complessi test funzionali.
Perché usare PyTest?
Alcuni dei vantaggi di pytest sono
- Molto facile da iniziare grazie alla sua sintassi semplice e facile.
- Può eseguire test in parallelo.
- Può eseguire un test specifico o un sottoinsieme di test
- Rileva automaticamente i test
- Salta i test
- Open source
Come installare PyTest
Di seguito è riportata la procedura per installare PyTest:
Passo 1) Puoi installare pytest tramite
pip install pytest==2.9.1
Una volta completata l'installazione è possibile confermarla con
py.test -h
Verrà visualizzato l'aiuto
Primo PyTest di base
Ora impareremo come utilizzare Pytest con un esempio PyTest di base.
Crea una cartella study_pytest. Creeremo i nostri file di test all'interno di questa cartella.
Vai a quella cartella nella riga di comando.
Crea un file denominato test_sample1.py all'interno della cartella
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"
Esegui il test utilizzando il comando
py.test
Otterrai un output come
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.
F dice fallimento
Il punto(.) indica il successo.
Nella sezione degli errori è possibile visualizzare i metodi non riusciti e la riga di errore. Qui x==y significa 5==6 che è falso.
Successivamente in questo tutorial su PyTest, impareremo l'asserzione in PyTest.
Affermazioni in PyTest
Le asserzioni Pytest sono controlli che restituiscono lo stato Vero o Falso. In Python Pytest, se un'asserzione fallisce in un metodo di test, l'esecuzione del metodo viene interrotta lì. Il codice rimanente in quel metodo di test non viene eseguito e le asserzioni Pytest continueranno con il metodo di test successivo.
Esempi di Pytest Assert:
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.
Prendere in considerazione
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Inserisci questo codice in test_file1_method1() invece dell'asserzione
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 i file di test. Possiamo menzionare esplicitamente altri nomi di file (spiegato più avanti). Pytest richiede che i nomi dei metodi di test inizino con "test.” Tutti gli altri nomi di metodi verranno ignorati anche se chiediamo esplicitamente di eseguirli.
Vedi 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 scegliere testlogin.py e logintest.py
Vedi 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 test_sample1.py. Supponiamo di avere più file, ad esempio test_sample2.py, test_sample3.py. Per eseguire tutti i test da tutti i file nella cartella e nelle sottocartelle dobbiamo semplicemente eseguire il comando pytest.
py.test
Verranno eseguiti tutti i nomi di file che iniziano con test_ e i nomi di file che terminano con _test in quella cartella e nelle sottocartelle in quella cartella.
Per eseguire test solo da un file specifico, possiamo utilizzare py.test
py.test test_sample1.py
Esegui un sottoinsieme di Intero test con PyTest
A volte non vogliamo eseguire l'intera suite di test. Pytest ci consente di eseguire test specifici. Possiamo farlo in 2 modi
- Raggruppamento di nomi di test in base alla corrispondenza di sottostringhe
- Raggruppamento dei test per marcatori
Abbiamo già 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 lo abbiamo attualmente
• test_sample1.py • test_file1_method1() • test_file1_method2() • test_sample2.py • test_file2_method1() • test_file2_method2()
Opzione 1) Esegui test in base alla corrispondenza delle sottostringhe
Qui per eseguire tutti i test che hanno metodo1 nel nome dobbiamo eseguire
py.test -k method1 -v -k <expression> is used to represent the substring to match -v increases the verbosity
Quindi eseguendo py.test -k method1 -v otterrai 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 correre con varie combinazioni come: -
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 consente di impostare vari attributi per i metodi di test utilizzando i marcatori pytest, @pytest.mark . Per utilizzare i marcatori nel file di test, dobbiamo importare pytest sui file di test.
Qui applicheremo nomi di marcatori diversi ai metodi di test ed eseguiremo test specifici in base ai nomi dei marcatori. Possiamo definire i marcatori sui nomi di ciascun test utilizzando
@pytest.mark.<name>.
Stiamo definendo i marcatori set1 e set2 sui metodi di test, ed eseguiamo il test utilizzando i nomi dei marcatori. Aggiorniamo 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 da
py.test -m <name> -m <name> mentions the marker name
Esegui py.test -m set1. Verranno eseguiti i metodi test_file1_method1, test_file2_method1, test_file2_method2.
L'esecuzione di py.test -m set2 eseguirà test_file1_method2.
Esegui test in parallelo con Pytest
Di solito, una suite di test avrà più file di test e centinaia di metodi di test la cui esecuzione richiederà una notevole quantità di tempo. Pytest ci consente di eseguire test in parallelo.
Per questo dobbiamo prima installare pytest-xdist eseguendo
pip install pytest-xdist
Puoi eseguire i test ora
py.test -n 4
-N esegue i test utilizzando più operatori. Nel comando precedente, ci saranno 4 lavoratori che eseguiranno il test.
Infissi Pytest
Le fixture vengono utilizzate quando vogliamo eseguire del codice prima di ogni metodo di test. Quindi, invece di ripetere lo stesso codice in ogni test, definiamo i dispositivi. Di solito, le apparecchiature vengono utilizzate per inizializzare le connessioni al database, passare la base, ecc
Un metodo viene contrassegnato come dispositivo Pytest contrassegnandolo con
@pytest.fixture
Un metodo di test può utilizzare un dispositivo Pytest menzionando il dispositivo 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 apparecchio denominato supply_AA_BB_CC. Questo metodo restituirà un elenco di 3 valori.
- Abbiamo 3 metodi di test che confrontano ciascuno dei valori.
Ciascuna funzione di test ha un argomento di input il cui nome corrisponde a un dispositivo disponibile. Pytest invoca quindi il metodo fixture corrispondente e i valori restituiti verranno archiviati nell'argomento di input, qui l'elenco [25,35,45]. Ora gli elementi dell'elenco vengono utilizzati nei metodi di test per il confronto.
Ora esegui il test e guarda 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 è stato superato poiché zz=BB=35 e i restanti 2 test sono falliti.
Il metodo fixture ha un ambito solo all'interno del file di test in cui è definito. Se proviamo ad accedere al dispositivo in qualche altro file di test, riceveremo un errore che dice dispositivo "supply_AA_BB_CC" non trovato per i metodi di prova in altri file.
Per utilizzare lo stesso dispositivo su più file di test, creeremo metodi di dispositivo in un file chiamato conftest.py.
Vediamolo con l'esempio PyTest qui sotto. Crea 3 file conftest.py, test_basic_fixture.py, test_basic_fixture2.py con il seguente codice
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 l'apparecchiatura nel file test e, se non la trova, cercherà in conftest.py
Esegui il test con py.test -k test_comparewith -v per ottenere il risultato come 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 è eseguire un test su più set di argomenti. Possiamo farlo tramite @pytest.mark.parametrize.
Lo vedremo con l'esempio PyTest riportato di seguito. Qui passeremo 3 argomenti a un metodo di test. Questo metodo di test aggiungerà i primi 2 argomenti e li confronterà con il 3° 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 3 argomenti: input1, input2, output. Aggiunge input1 e input2 e confronta con l'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
Puoi vedere i test eseguiti 2 volte: uno che controlla 5+5 ==10 e l'altro che controlla 3+5 ==12
test_addition.py::test_add[5-5-10] SUPERATO
test_addition.py::test_add[3-5-12] NON FALLITO
Pytest Xfail / Salta test
Ci saranno alcune situazioni in cui non vogliamo eseguire un test, o a caso di prova non è rilevante per un determinato momento. In queste situazioni, abbiamo la possibilità di fallire il test o saltare i test
Il test xfailed verrà eseguito, ma non verrà conteggiato come parte dei test non riusciti o superati. Non verrà visualizzato alcun traceback se il test fallisce. Possiamo xfail test utilizzando
@pytest.mark.xfail.
Saltare un test significa che il test non verrà eseguito. Possiamo saltare i test utilizzando
@pytest.mark.skip.
Modifica 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 fallimento del test) o xpassed (in caso di superamento del test). Non ci sarà alcuna traccia dei fallimenti.
- test_add_5 e test_add_6 verranno eseguiti e test_add_6 segnalerà l'errore con traceback mentre test_add_5 passa
Esegui il test con py.test test_addition.py -v e guarda 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 XML
Possiamo creare risultati di test in formato XML che possiamo inviare ai server di integrazione continua per ulteriori elaborazioni e così via. Questo può essere fatto da
py.test test_sample1.py -v –junitxml="risultato.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. Di seguito puoi vedere i dettagli riguardanti ogni test eseguito sotto etichetta.
Pytest Framework Test di un'API
Ora creeremo un piccolo framework pytest per testare un'API. L'API qui utilizzata è gratuita https://reqres.in/. Questo sito Web serve solo per fornire API testabili. Questo sito web non memorizza i nostri dati.
Qui scriveremo alcuni test per
- elencando alcuni utenti
- accedere con gli utenti
Crea i file seguenti con il codice fornito
conftest.py: dispone di un dispositivo che fornirà l'URL di base per tutti i metodi di test
import pytest @pytest.fixture def supply_url(): return "https://reqres.in/api"
test_list_user.py – contiene i metodi di test per elencare gli utenti validi e non validi
- test_list_valid_user verifica il recupero di utenti validi e verifica la risposta
- test_list_invaliduser verifica il recupero di utenti non validi e verifica 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
test_login_user.py – contiene metodi di test per testare la funzionalità di accesso.
- test_login_valid verifica il tentativo di accesso valido con e-mail e password
- test_login_no_password verifica il tentativo di accesso non valido senza passare la password
- test_login_no_email verifica il tentativo di accesso non valido senza passare l'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
Guarda il risultato come
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 vari output
Sommario
In questo tutorial di PyTest, abbiamo trattato
- Installa pytest usando installazione pip pytest=2.9.1
- Semplice programma pytest ed eseguilo con il comando py.test.
- Le dichiarazioni di asserzione, assert x==y, restituiranno True o False.
- Come pytest identifica file e metodi di test.
- Prova i file che iniziano con test_ o termina con _test
- Metodi di prova a partire da test
- Il comando py.test eseguirà tutti i file di test in quella cartella e nelle sottocartelle. Per eseguire un file specifico, possiamo usare il comando py.test
- Eseguire un sottoinsieme di metodi di test
- Raggruppamento di nomi di test per sottostringa match.py.test -k -v eseguirà tutti i test avendo nel suo nome.
- Esegui test tramite marcatori. Contrassegna i test utilizzando @pytest.mark. ed esegui i test utilizzando pytest -m per eseguire i test contrassegnati come .
- Esegui test in parallelo
- Installa pytest-xdist utilizzando pip install pytest-xdist
- Esegui i test utilizzando py.test -n NUM dove NUM è il numero di lavoratori
- Creazione di metodi fixture per eseguire il codice prima di ogni test contrassegnando il metodo con @pytest.fixture
- L'ambito di un metodo di fissaggio è all'interno del file in cui è definito.
- È possibile accedere a un metodo di fissaggio da più file di test definendolo nel file conftest.py.
- Un metodo di test può accedere a un dispositivo Pytest utilizzandolo come argomento di input.
- Test di parametrizzazione per eseguirlo su più set di input.
@pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
def prova_add(ingresso1, ingresso2, uscita):
affermare input1+input2 == output,"non riuscito"
eseguirà il test con gli input (5,5,10) e (3,5,12) - Salta/xfail test utilizzando @pytets.mark.skip e @pytest.mark.xfail
- Crea risultati di test in formato XML che coprono i dettagli del test eseguito utilizzando py.test test_sample1.py -v –junitxml=”result.xml”
- Un framework pytest di esempio per testare un'API