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



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow