Python Language
py.test
Ricerca…
Impostazione di py.test
py.test
è una delle numerose librerie di test di terze parti disponibili per Python. Può essere installato usando pip
con
pip install pytest
Il codice da testare
Diciamo che stiamo testando una funzione di addizione in projectroot/module/code.py
:
# projectroot/module/code.py
def add(a, b):
return a + b
Il codice di prova
Creiamo un file di test in projectroot/tests/test_code.py
. Il file deve iniziare con test_
per essere riconosciuto come un file di test.
# projectroot/tests/test_code.py
from module import code
def test_add():
assert code.add(1, 2) == 3
Esecuzione del test
Da projectroot
eseguiamo semplicemente py.test
:
# ensure we have the modules
$ touch tests/__init__.py
$ touch module/__init__.py
$ py.test
================================================== test session starts ===================================================
platform darwin -- Python 2.7.10, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /projectroot, inifile:
collected 1 items
tests/test_code.py .
================================================ 1 passed in 0.01 seconds ================================================
Test falliti
Un test non funzionante fornirà un utile risultato su ciò che è andato storto:
# projectroot/tests/test_code.py
from module import code
def test_add__failing():
assert code.add(10, 11) == 33
risultati:
$ py.test
================================================== test session starts ===================================================
platform darwin -- Python 2.7.10, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /projectroot, inifile:
collected 1 items
tests/test_code.py F
======================================================== FAILURES ========================================================
___________________________________________________ test_add__failing ____________________________________________________
def test_add__failing():
> assert code.add(10, 11) == 33
E assert 21 == 33
E + where 21 = <function add at 0x105d4d6e0>(10, 11)
E + where <function add at 0x105d4d6e0> = code.add
tests/test_code.py:5: AssertionError
================================================ 1 failed in 0.01 seconds ================================================
Introduzione alle partite di prova
Talvolta i test più complicati devono essere impostati prima di eseguire il codice che si desidera testare. È possibile farlo nella stessa funzione di test, ma poi si finisce con le funzioni di test di grandi dimensioni facendo così tanto che è difficile dire dove si ferma l'installazione e inizia il test. Puoi anche ottenere un sacco di codice di duplicazione tra le varie funzioni di test.
Il nostro file di codice:
# projectroot/module/stuff.py
class Stuff(object):
def prep(self):
self.foo = 1
self.bar = 2
Il nostro file di test:
# projectroot/tests/test_stuff.py
import pytest
from module import stuff
def test_foo_updates():
my_stuff = stuff.Stuff()
my_stuff.prep()
assert 1 == my_stuff.foo
my_stuff.foo = 30000
assert my_stuff.foo == 30000
def test_bar_updates():
my_stuff = stuff.Stuff()
my_stuff.prep()
assert 2 == my_stuff.bar
my_stuff.bar = 42
assert 42 == my_stuff.bar
Questi sono esempi piuttosto semplici, ma se il nostro oggetto Stuff
aveva bisogno di molte più impostazioni, sarebbe ingombrante. Vediamo che c'è un codice duplicato tra i nostri casi di test, quindi cerchiamo di farlo prima in una funzione separata.
# projectroot/tests/test_stuff.py
import pytest
from module import stuff
def get_prepped_stuff():
my_stuff = stuff.Stuff()
my_stuff.prep()
return my_stuff
def test_foo_updates():
my_stuff = get_prepped_stuff()
assert 1 == my_stuff.foo
my_stuff.foo = 30000
assert my_stuff.foo == 30000
def test_bar_updates():
my_stuff = get_prepped_stuff()
assert 2 == my_stuff.bar
my_stuff.bar = 42
assert 42 == my_stuff.bar
Questo sembra migliore, ma abbiamo ancora la my_stuff = get_prepped_stuff()
che ingombra le nostre funzioni di test.
py.test infissi in soccorso!
Le fixture sono versioni molto più potenti e flessibili delle funzioni di setup del test. Possono fare molto più di quanto stiamo facendo leva qui, ma lo faremo un passo alla volta.
Per prima cosa cambiamo get_prepped_stuff
in una fixture chiamata prepped_stuff
. Vuoi nominare i tuoi dispositivi con nomi piuttosto che verbi a causa del modo in cui i proiettori finiranno per essere usati nelle funzioni di test più avanti. La @pytest.fixture
indica che questa funzione specifica dovrebbe essere gestita come un dispositivo @pytest.fixture
piuttosto che una funzione regolare.
@pytest.fixture
def prepped_stuff():
my_stuff = stuff.Stuff()
my_stuff.prep()
return my_stuff
Ora dovremmo aggiornare le funzioni di test in modo che utilizzino il dispositivo. Questo viene fatto aggiungendo un parametro alla loro definizione che corrisponde esattamente al nome della fixture. Quando py.test viene eseguito, eseguirà la fixture prima di eseguire il test, quindi passerà il valore restituito dalla fixture alla funzione di test attraverso quel parametro. (Si noti che le fixture non hanno bisogno di restituire un valore, ma possono fare altre cose di setup, come chiamare una risorsa esterna, organizzare le cose sul filesystem, mettere i valori in un database, qualunque sia il test necessario per l'installazione)
def test_foo_updates(prepped_stuff):
my_stuff = prepped_stuff
assert 1 == my_stuff.foo
my_stuff.foo = 30000
assert my_stuff.foo == 30000
def test_bar_updates(prepped_stuff):
my_stuff = prepped_stuff
assert 2 == my_stuff.bar
my_stuff.bar = 42
assert 42 == my_stuff.bar
Ora puoi capire perché l'abbiamo chiamato con un nome. ma la linea my_stuff = prepped_stuff
è praticamente inutile, quindi usiamo invece prepped_stuff
direttamente.
def test_foo_updates(prepped_stuff):
assert 1 == prepped_stuff.foo
prepped_stuff.foo = 30000
assert prepped_stuff.foo == 30000
def test_bar_updates(prepped_stuff):
assert 2 == prepped_stuff.bar
prepped_stuff.bar = 42
assert 42 == prepped_stuff.bar
Ora stiamo usando i dispositivi! Possiamo andare oltre cambiando l'ambito della fixture (quindi viene eseguito solo una volta per modulo di test o sessione di esecuzione della suite di test invece di una volta per funzione di test), costruendo dispositivi che utilizzano altri dispositivi, parametrizzando l'apparecchiatura (in modo che l'apparecchiatura e tutti i test che utilizzano quel dispositivo vengono eseguiti più volte, una volta per ogni parametro assegnato all'apparecchio), i dispositivi che leggono i valori dal modulo che li chiama ... come accennato in precedenza, gli apparecchi hanno molta più potenza e flessibilità di una normale funzione di configurazione.
Pulizia dopo i test.
Diciamo che il nostro codice è cresciuto e il nostro oggetto Stuff ora ha bisogno di una pulizia speciale.
# projectroot/module/stuff.py
class Stuff(object):
def prep(self):
self.foo = 1
self.bar = 2
def finish(self):
self.foo = 0
self.bar = 0
Potremmo aggiungere del codice per chiamare la pulizia nella parte inferiore di ogni funzione di test, ma i dispositivi forniscono un modo migliore per farlo. Se si aggiunge una funzione all'apparecchio e lo si registra come finalizzatore , il codice nella funzione finalizzatore verrà chiamato dopo che il test con l'apparecchiatura è stato eseguito. Se l'ambito della fixture è più grande di una singola funzione (come il modulo o la sessione), il finalizzatore verrà eseguito dopo che tutti i test in ambito sono stati completati, quindi dopo il completamento del modulo o alla fine dell'intera sessione di test in esecuzione .
@pytest.fixture
def prepped_stuff(request): # we need to pass in the request to use finalizers
my_stuff = stuff.Stuff()
my_stuff.prep()
def fin(): # finalizer function
# do all the cleanup here
my_stuff.finish()
request.addfinalizer(fin) # register fin() as a finalizer
# you can do more setup here if you really want to
return my_stuff
Usare la funzione finalizzatore all'interno di una funzione può essere un po 'difficile da capire a prima vista, specialmente quando si hanno proiettori più complicati. Puoi invece usare un dispositivo di rendimento per fare la stessa cosa con un flusso di esecuzione più leggibile. L'unica vera differenza è che invece di usare return
usiamo un yield
nella parte della fixture in cui viene eseguita la configurazione e il controllo dovrebbe andare a una funzione di test, quindi aggiungere tutto il codice cleanup dopo la yield
. Lo decoriamo anche come yield_fixture
modo che py.test sappia come gestirlo.
@pytest.yield_fixture
def prepped_stuff(): # it doesn't need request now!
# do setup
my_stuff = stuff.Stuff()
my_stuff.prep()
# setup is done, pass control to the test functions
yield my_stuff
# do cleanup
my_stuff.finish()
E questo conclude l'Intro to Test Fixtures!
Per ulteriori informazioni, consultare la documentazione ufficiale su py.test e la documentazione ufficiale sulla resa