Pytest - Guida rapida

Pytest è un framework di test basato su Python, che viene utilizzato per scrivere ed eseguire codici di test. Al giorno d'oggi dei servizi REST, pytest viene utilizzato principalmente per i test API anche se possiamo usare pytest per scrivere test semplici o complessi, cioè possiamo scrivere codici per testare API, database, UI, ecc.

Vantaggi di Pytest

I vantaggi di Pytest sono i seguenti:

  • Pytest può eseguire più test in parallelo, il che riduce il tempo di esecuzione della suite di test.

  • Pytest ha il suo modo di rilevare automaticamente il file di test e le funzioni di test, se non menzionato esplicitamente.

  • Pytest ci consente di saltare un sottoinsieme dei test durante l'esecuzione.

  • Pytest ci consente di eseguire un sottoinsieme dell'intera suite di test.

  • Pytest è gratuito e open source.

  • A causa della sua semplice sintassi, pytest è molto facile da iniziare.

In questo tutorial, spiegheremo i fondamenti più importanti con programmi di esempio.

In questo capitolo impareremo come installare pytest.

Per avviare l'installazione, eseguire il seguente comando:

pip install pytest == 2.9.1

Possiamo installare qualsiasi versione di pytest. Qui, 2.9.1 è la versione che stiamo installando.

Per installare l'ultima versione di pytest, esegui il seguente comando:

pip install pytest

Confermare l'installazione utilizzando il seguente comando per visualizzare la sezione della guida di pytest.

pytest -h

L'esecuzione di pytest senza menzionare un nome di file eseguirà tutti i file di formato test_*.py o *_test.pynella directory e nelle sottodirectory correnti. Pytest identifica automaticamente quei file come file di prova. Noican fai in modo che pytest esegua altri nomi di file menzionandoli esplicitamente.

Pytest richiede che i nomi delle funzioni di test inizino con test. Nomi di funzioni che non sono di formatotest*non sono considerate funzioni di test da pytest. Noicannot rendere esplicitamente pytest considerare qualsiasi funzione che non inizi con test come funzione di test.

Comprenderemo l'esecuzione dei test nei capitoli successivi.

Ora inizieremo con il nostro primo programma pytest. Creeremo prima una directory e quindi creeremo i nostri file di test nella directory.

Seguiamo i passaggi mostrati di seguito:

  • Crea una nuova directory denominata automation e accedi alla directory nella riga di comando.

  • Crea un file denominato test_square.py e aggiungi il codice seguente a quel file.

import math

def test_sqrt():
   num = 25
   assert math.sqrt(num) == 5

def testsquare():
   num = 7
   assert 7*7 == 40

def tesequality():
   assert 10 == 11

Esegui il test utilizzando il seguente comando:

pytest

Il comando precedente genererà il seguente output:

test_square.py .F
============================================== FAILURES 
==============================================
______________________________________________ testsquare 
_____________________________________________
   def testsquare():
   num=7
>  assert 7*7 == 40
E  assert (7 * 7) == 40
test_square.py:9: AssertionError
================================= 1 failed, 1 passed in 0.06 seconds 
=================================

Vedi la prima riga del risultato. Visualizza il nome del file e i risultati. F rappresenta un fallimento del test e il punto (.) Rappresenta un successo del test.

Di seguito, possiamo vedere i dettagli dei test falliti. Mostrerà in quale affermazione il test è fallito. Nel nostro esempio, 7 * 7 viene confrontato per l'uguaglianza contro 40, il che è sbagliato. Alla fine, possiamo vedere il riepilogo dell'esecuzione del test, 1 non riuscito e 1 superato.

La funzione tesequality non viene eseguita perché pytest non la considererà come un test poiché il suo nome non è del formato test*.

Ora, esegui il comando seguente e guarda di nuovo il risultato -

pytest -v

-v aumenta la verbosità.

test_square.py::test_sqrt PASSED
test_square.py::testsquare FAILED
============================================== FAILURES 
==============================================
_____________________________________________ testsquare 
_____________________________________________
   def testsquare():
   num = 7
>  assert 7*7 == 40
E  assert (7 * 7) == 40
test_square.py:9: AssertionError
================================= 1 failed, 1 passed in 0.04 seconds 
=================================

Ora il risultato è più esplicativo sul test non riuscito e sul test superato.

Note - Il comando pytest eseguirà tutti i file di formato test_* o *_test nella directory e nelle sottodirectory correnti.

In questo capitolo impareremo come eseguire un singolo file di test e più file di test. Abbiamo già un file di provatest_square.pycreato. Crea un nuovo file di provatest_compare.py con il seguente codice -

def test_greater():
   num = 100
   assert num > 100

def test_greater_equal():
   num = 100
   assert num >= 100

def test_less():
   num = 100
   assert num < 200

Ora per eseguire tutti i test da tutti i file (2 file qui) dobbiamo eseguire il seguente comando:

pytest -v

Il comando precedente eseguirà i test da entrambi test_square.py e test_compare.py. L'output verrà generato come segue:

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
test_compare.py::test_less PASSED
test_square.py::test_sqrt PASSED
test_square.py::testsquare FAILED
================================================ FAILURES 
================================================
______________________________________________ test_greater 
______________________________________________
   def test_greater():
   num = 100
>  assert num > 100
E  assert 100 > 100

test_compare.py:3: AssertionError
_______________________________________________ testsquare 
_______________________________________________
   def testsquare():
   num = 7
>  assert 7*7 == 40
E  assert (7 * 7) == 40

test_square.py:9: AssertionError
=================================== 2 failed, 3 passed in 0.07 seconds 
===================================

Per eseguire i test da un file specifico, utilizzare la seguente sintassi:

pytest <filename> -v

Ora, esegui il seguente comando:

pytest test_compare.py -v

Il comando precedente eseguirà i test solo da file test_compare.py. Il nostro risultato sarà:

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
test_compare.py::test_less PASSED
============================================== FAILURES 
==============================================
____________________________________________ test_greater 
____________________________________________
   def test_greater():
   num = 100
>  assert num > 100
E  assert 100 > 100
test_compare.py:3: AssertionError
================================= 1 failed, 2 passed in 0.04 seconds 
=================================

In uno scenario reale, avremo più file di test e ogni file avrà un numero di test. I test riguarderanno vari moduli e funzionalità. Supponiamo di voler eseguire solo un insieme specifico di test; Come facciamo a tal proposito?

Pytest offre due modi per eseguire il sottoinsieme della suite di test.

  • Selezionare i test da eseguire in base alla corrispondenza della sottostringa dei nomi dei test.
  • Seleziona i gruppi di test da eseguire in base ai marcatori applicati.

Spiegheremo questi due con esempi nei capitoli successivi.

Per eseguire i test contenenti una stringa nel suo nome possiamo utilizzare la seguente sintassi:

pytest -k <substring> -v

-k <substring> rappresenta la sottostringa da cercare nei nomi dei test.

Ora, esegui il seguente comando:

pytest -k great -v

Questo eseguirà tutti i nomi di test contenenti la parola ‘great’nel suo nome. In questo caso, lo sonotest_greater() e test_greater_equal(). Vedi il risultato sotto.

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
============================================== FAILURES 
==============================================
____________________________________________ test_greater 
____________________________________________
def test_greater():
num = 100
>  assert num > 100
E  assert 100 > 100
test_compare.py:3: AssertionError
========================== 1 failed, 1 passed, 3 deselected in 0.07 seconds 
==========================

Qui nel risultato possiamo vedere 3 test deselezionati. Questo perché quei nomi di test non contengono la parolagreat in loro.

Note - Il nome della funzione di test dovrebbe comunque iniziare con "test".

In questo capitolo impareremo come raggruppare i test usando i marcatori.

Pytest ci consente di utilizzare i marker sulle funzioni di test. I marcatori vengono utilizzati per impostare varie caratteristiche / attributi per testare le funzioni. Pytest fornisce molti marcatori integrati come xfail, skip e parametrize. Oltre a ciò, gli utenti possono creare i propri nomi di marker. I marcatori vengono applicati sui test utilizzando la sintassi indicata di seguito:

@pytest.mark.<markername>

Per usare i marker, dobbiamo import pytestmodulo nel file di test. Possiamo definire i nostri nomi di marker per i test ed eseguire i test con quei nomi di marker.

Per eseguire i test contrassegnati, possiamo utilizzare la seguente sintassi:

pytest -m <markername> -v

-m <markername> rappresenta il nome del marker dei test da eseguire.

Aggiorna i nostri file di prova test_compare.py e test_square.pycon il seguente codice. Stiamo definendo 3 marker– great, square, others.

test_compare.py

import pytest
@pytest.mark.great
def test_greater():
   num = 100
   assert num > 100

@pytest.mark.great
def test_greater_equal():
   num = 100
   assert num >= 100

@pytest.mark.others
def test_less():
   num = 100
   assert num < 200

test_square.py

import pytest
import math

@pytest.mark.square
def test_sqrt():
   num = 25
   assert math.sqrt(num) == 5

@pytest.mark.square
def testsquare():
   num = 7
   assert 7*7 == 40

@pytest.mark.others
   def test_equality():
   assert 10 == 11

Ora per eseguire i test contrassegnati come others, esegui il seguente comando:

pytest -m others -v

Vedi il risultato sotto. Ha eseguito i 2 test contrassegnati comeothers.

test_compare.py::test_less PASSED
test_square.py::test_equality FAILED
============================================== FAILURES
==============================================
___________________________________________ test_equality
____________________________________________
   @pytest.mark.others
   def test_equality():
>  assert 10 == 11
E  assert 10 == 11
test_square.py:16: AssertionError
========================== 1 failed, 1 passed, 4 deselected in 0.08 seconds
==========================

Allo stesso modo, possiamo eseguire test anche con altri marker: ottimo, confronta

I dispositivi sono funzioni che verranno eseguite prima di ciascuna funzione di test a cui vengono applicate. I dispositivi vengono utilizzati per fornire alcuni dati ai test come connessioni al database, URL da testare e una sorta di dati di input. Pertanto, invece di eseguire lo stesso codice per ogni test, possiamo allegare la funzione fixture ai test e verrà eseguita e restituirà i dati al test prima di eseguire ogni test.

Una funzione è contrassegnata come dispositivo da:

@pytest.fixture

Una funzione di test può utilizzare un'apparecchiatura citando il nome dell'apparecchiatura come parametro di input.

Crea un file test_div_by_3_6.py e aggiungi il codice seguente

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

Qui abbiamo una funzione dispositivo chiamata input_value, che fornisce l'input ai test. Per accedere alla funzione fixture, i test devono menzionare il nome fixture come parametro di input.

Durante l'esecuzione del test, Pytest vedrà il nome del dispositivo come parametro di input. Quindi esegue la funzione dispositivo e il valore restituito viene memorizzato nel parametro di input, che può essere utilizzato dal test.

Eseguire il test utilizzando il seguente comando:

pytest -k divisible -v

Il comando precedente genererà il seguente risultato:

test_div_by_3_6.py::test_divisible_by_3 PASSED
test_div_by_3_6.py::test_divisible_by_6 FAILED
============================================== FAILURES
==============================================
________________________________________ test_divisible_by_6
_________________________________________
input_value = 39
   def test_divisible_by_6(input_value):
>  assert input_value % 6 == 0
E  assert (39 % 6) == 0
test_div_by_3_6.py:12: AssertionError
========================== 1 failed, 1 passed, 6 deselected in 0.07 seconds
==========================

Tuttavia, l'approccio ha i suoi limiti. Una funzione dispositivo definita all'interno di un file di test ha un ambito solo all'interno del file di test. Non possiamo usare quel dispositivo in un altro file di test. Per rendere disponibile una fixture a più file di test, dobbiamo definire la funzione fixture in un file chiamato conftest.py.conftest.py è spiegato nel prossimo capitolo.

Possiamo definire le funzioni del dispositivo in questo file per renderle accessibili su più file di prova.

Crea un nuovo file conftest.py e aggiungi il codice seguente in esso -

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

Modifica il file test_div_by_3_6.py per rimuovere la funzione faro -

import pytest

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

Crea un nuovo file test_div_by_13.py -

import pytest

def test_divisible_by_13(input_value):
   assert input_value % 13 == 0

Ora abbiamo i file test_div_by_3_6.py e test_div_by_13.py facendo uso del dispositivo definito in conftest.py.

Esegui i test eseguendo il seguente comando:

pytest -k divisible -v

Il comando precedente genererà il seguente risultato:

test_div_by_13.py::test_divisible_by_13 PASSED
test_div_by_3_6.py::test_divisible_by_3 PASSED
test_div_by_3_6.py::test_divisible_by_6 FAILED
============================================== FAILURES
==============================================
________________________________________ test_divisible_by_6
_________________________________________
input_value = 39
   def test_divisible_by_6(input_value):
>  assert input_value % 6 == 0
E  assert (39 % 6) == 0
test_div_by_3_6.py:7: AssertionError
========================== 1 failed, 2 passed, 6 deselected in 0.09 seconds
==========================

I test cercheranno il dispositivo nello stesso file. Poiché il dispositivo non si trova nel file, controllerà il dispositivo nel file conftest.py. Quando lo trova, viene richiamato il metodo fixture e il risultato viene restituito all'argomento di input del test.

La parametrizzazione di un test viene eseguita per eseguire il test su più set di input. Possiamo farlo utilizzando il seguente indicatore:

@pytest.mark.parametrize

Copia il codice seguente in un file chiamato test_multiplication.py -

import pytest

@pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)])
def test_multiplication_11(num, output):
   assert 11*num == output

Qui il test moltiplica un input per 11 e confronta il risultato con l'output atteso. Il test ha 4 serie di input, ciascuno ha 2 valori: uno è il numero da moltiplicare per 11 e l'altro è il risultato atteso.

Esegui il test eseguendo il seguente comando:

Pytest -k multiplication -v

Il comando precedente genererà il seguente output:

test_multiplication.py::test_multiplication_11[1-11] PASSED
test_multiplication.py::test_multiplication_11[2-22] PASSED
test_multiplication.py::test_multiplication_11[3-35] FAILED
test_multiplication.py::test_multiplication_11[4-44] PASSED
============================================== FAILURES
==============================================
_________________ test_multiplication_11[3-35] __________________
num = 3, output = 35
   @pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)])
   def test_multiplication_11(num, output):
>  assert 11*num == output
E  assert (11 * 3) == 35
test_multiplication.py:5: AssertionError
============================== 1 failed, 3 passed, 8 deselected in 0.08 seconds
==============================

In questo capitolo impareremo i test Skip e Xfail in Pytest.

Ora, considera le seguenti situazioni:

  • Un test non è rilevante per qualche tempo a causa di alcuni motivi.
  • È in fase di implementazione una nuova funzionalità e abbiamo già aggiunto un test per quella funzionalità.

In queste situazioni, abbiamo la possibilità di fallire il test o saltare i test.

Pytest eseguirà il test xfailed, ma non sarà considerato come test parzialmente fallito o superato. I dettagli di questi test non verranno stampati anche se il test fallisce (ricorda che pytest di solito stampa i dettagli del test fallito). Possiamo xfail test usando il seguente marker:

@pytest.mark.xfail

Saltare un test significa che il test non verrà eseguito. Possiamo saltare i test usando il seguente marcatore:

@pytest.mark.skip

Successivamente, quando il test diventa rilevante, possiamo rimuovere i marcatori.

Modifica il file test_compare.py dobbiamo già includere i marcatori xfail e skip -

import pytest
@pytest.mark.xfail
@pytest.mark.great
def test_greater():
   num = 100
   assert num > 100

@pytest.mark.xfail
@pytest.mark.great
def test_greater_equal():
   num = 100
   assert num >= 100

@pytest.mark.skip
@pytest.mark.others
def test_less():
   num = 100
   assert num < 200

Eseguire il test utilizzando il seguente comando:

pytest test_compare.py -v

Al momento dell'esecuzione, il comando precedente genererà il seguente risultato:

test_compare.py::test_greater xfail
test_compare.py::test_greater_equal XPASS
test_compare.py::test_less SKIPPED
============================ 1 skipped, 1 xfailed, 1 xpassed in 0.06 seconds
============================

In uno scenario reale, una volta che una nuova versione del codice è pronta per la distribuzione, viene prima distribuita nell'ambiente di pre-produzione / gestione temporanea. Quindi viene eseguita una suite di test.

Il codice è qualificato per la distribuzione in produzione solo se la suite di test ha esito positivo. Se si verifica un errore del test, che sia uno o più, il codice non è pronto per la produzione.

Quindi, cosa succederebbe se volessimo interrompere l'esecuzione della suite di test subito dopo n numero di test falliti. Questo può essere fatto in pytest usando maxfail.

La sintassi per interrompere l'esecuzione della suite di test subito dopo n numero di test falliti è la seguente:

pytest --maxfail = <num>

Crea un file test_failure.py con il codice seguente.

import pytest
import math

def test_sqrt_failure():
   num = 25
   assert math.sqrt(num) == 6

def test_square_failure():
   num = 7
   assert 7*7 == 40

def test_equality_failure():
   assert 10 == 11

Tutti e 3 i test falliranno durante l'esecuzione di questo file di test. Qui, interromperemo l'esecuzione del test dopo un errore stesso da:

pytest test_failure.py -v --maxfail = 1
test_failure.py::test_sqrt_failure FAILED
=================================== FAILURES
=================================== _______________________________________
test_sqrt_failure __________________________________________
   def test_sqrt_failure():
   num = 25
>  assert math.sqrt(num) == 6
E  assert 5.0 == 6
E  + where 5.0 = <built-in function sqrt>(25)
E  + where <built-in function sqrt>= math.sqrt
test_failure.py:6: AssertionError
=============================== 1 failed in 0.04 seconds
===============================

Nel risultato sopra, possiamo vedere che l'esecuzione viene interrotta in caso di errore.

Per impostazione predefinita, pytest esegue i test in ordine sequenziale. In uno scenario reale, una suite di test avrà un numero di file di test e ogni file avrà una serie di test. Ciò porterà a un lungo tempo di esecuzione. Per ovviare a questo problema, pytest ci offre un'opzione per eseguire test in parallelo.

Per questo, dobbiamo prima installare il plugin pytest-xdist.

Installa pytest-xdist eseguendo il seguente comando:

pip install pytest-xdist

Ora possiamo eseguire test usando la sintassi pytest -n <num>

pytest -n 3

-n <num> esegue i test utilizzando più worker, eccolo 3.

Non avremo molta differenza di tempo quando ci saranno solo pochi test da eseguire. Tuttavia, è importante quando la suite di test è grande.

Possiamo generare i dettagli dell'esecuzione del test in un file xml. Questo file xml è utile principalmente nei casi in cui abbiamo una dashboard che proietta i risultati del test. In questi casi, l'xml può essere analizzato per ottenere i dettagli dell'esecuzione.

Ora eseguiremo i test da test_multiplcation.py e genereremo l'xml eseguendo

pytest test_multiplication.py -v --junitxml="result.xml"

Ora possiamo vedere che result.xml viene generato con i seguenti dati:

<?xml version = "1.0" encoding = "utf-8"?>
<testsuite errors = "0" failures = "1"
name = "pytest" skips = "0" tests = "4" time = "0.061">
   <testcase classname = "test_multiplication"          
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[1-11]"
      time = "0.00117516517639>
   </testcase>
   
   <testcase classname = "test_multiplication"    
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[2-22]"
      time = "0.00155973434448">
   </testcase>

   <testcase classname = "test_multiplication" 
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[3-35]" time = "0.00144290924072">
      failure message = "assert (11 * 3) == 35">num = 3, output = 35

         @pytest.mark.parametrize("num,
         output",[(1,11),(2,22),(3,35),(4,44)])
            
         def test_multiplication_11(num, output):> 
         assert 11*num == output
         E assert (11 * 3) == 35

         test_multiplication.py:5: AssertionErro
      </failure>
   </testcase>
   <testcase classname = "test_multiplication" 
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[4-44]"
      time = "0.000945091247559">
   </testcase>
</testsuite>

Qui, il tag <testsuit> riassume che ci sono stati 4 test e il numero di fallimenti è 1.

  • Il tag <testcase> fornisce i dettagli di ogni test eseguito.

  • Il tag <failure> fornisce i dettagli del codice di test fallito.

In questo tutorial pytest, abbiamo coperto le seguenti aree:

  • Installazione di pytest ..
  • Identificazione dei file di test e delle funzioni di test.
  • Esecuzione di tutti i file di test utilizzando pytest –v.
  • Esecuzione di un file specifico usando pytest <nomefile> -v.
  • Eseguire i test mediante sottostringa corrispondente a pytest -k <substring> -v.
  • Esegui i test in base ai marker pytest -m <marker_name> -v.
  • Creazione di dispositivi utilizzando @ pytest.fixture.
  • conftest.py consente di accedere ai dispositivi da più file.
  • Parametrizzazione dei test utilizzando @ pytest.mark.parametrize.
  • Xfailing test utilizzando @ pytest.mark.xfail.
  • Saltare i test utilizzando @ pytest.mark.skip.
  • Interrompi l'esecuzione del test su n fallimenti usando pytest --maxfail = <num>.
  • Esecuzione di test in parallelo utilizzando pytest -n <num>.
  • Generazione di risultati xml utilizzando pytest -v --junitxml = "result.xml".

Questo tutorial ti ha introdotto al framework pytest. Ora dovresti essere in grado di iniziare a scrivere test usando pytest.

Come buona pratica -

  • Creare diversi file di test in base alla funzionalità / al modulo in fase di test.
  • Assegna nomi significativi per testare file e metodi.
  • Avere abbastanza marcatori per raggruppare i test in base a vari criteri.
  • Usa i dispositivi ogni volta che è necessario.