Sök…


Ställa in py.test

py.test är ett av flera py.test från tredje part som är tillgängliga för Python. Det kan installeras med pip med

pip install pytest

Koden som ska testas

Säg att vi testar en tilläggsfunktion i projectroot/module/code.py :

# projectroot/module/code.py
def add(a, b):
    return a + b

Testkoden

Vi skapar en testfil i projectroot/tests/test_code.py . Filen måste börja med test_ för att erkännas som en testfil.

# projectroot/tests/test_code.py
from module import code


def test_add():
    assert code.add(1, 2) == 3

Kör testet

Från projectroot kör vi helt enkelt 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 ================================================

Misslyckade test

Ett misslyckande test ger bra resultat om vad som gick fel:

# projectroot/tests/test_code.py
from module import code


def test_add__failing():
    assert code.add(10, 11) == 33

Resultat:

$ 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 ================================================

Introduktion till testfixturer

Mer komplicerade tester behöver ibland ha saker inställda innan du kör koden du vill testa. Det är möjligt att göra detta i själva testfunktionen, men då hamnar du med stora testfunktioner som gör så mycket att det är svårt att se var installationen slutar och testet börjar. Du kan också få en hel del duplicerad installationskod mellan dina olika testfunktioner.

Vår kodfil:

# projectroot/module/stuff.py
class Stuff(object):
    def prep(self):
        self.foo = 1
        self.bar = 2

Vår testfil:

# 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

Dessa är ganska enkla exempel, men om vårt Stuff objekt behövde mycket mer installation, skulle det bli svårt. Vi ser att det finns en del duplicerad kod mellan våra testfall, så låt oss refaktorera det till en separat funktion först.

# 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

Det här ser bättre ut, men vi har fortfarande my_stuff = get_prepped_stuff() -samtalet med våra testfunktioner.

py.test fixturer till undsättning!

Fixturer är mycket kraftfullare och flexibla versioner av testinställningsfunktioner. De kan göra mycket mer än vi utnyttjar här, men vi tar det ett steg i taget.

Först byter vi get_prepped_stuff till en fixtur som kallas prepped_stuff . Du vill namnge dina fixturer med substantiv snarare än verb på grund av hur fixturerna kommer att hamna i testfunktionerna senare. @pytest.fixture indikerar att den här specifika funktionen bör hanteras som en fixtur snarare än en vanlig funktion.

@pytest.fixture
def prepped_stuff():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    return my_stuff

Nu bör vi uppdatera testfunktionerna så att de använder fixturen. Detta görs genom att lägga till en parameter till deras definition som exakt matchar fixturnamnet. När py.test körs kommer den att köra fixturen innan testet körs och sedan överföra fixturets returvärde till testfunktionen genom den parametern. (Observera att fixturer inte behöver returnera ett värde; de kan göra andra konfigurationssaker istället, som att ringa en extern resurs, ordna saker i filsystemet, lägga värden i en databas, oavsett testens behov för installation)

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

Nu kan du se varför vi namngav det med ett substantiv. men my_stuff = prepped_stuff linjen är ganska mycket värdelös, så låt oss bara använda prepped_stuff direkt istället.

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

Nu använder vi fixturer! Vi kan gå längre genom att ändra fixturens räckvidd (så att den bara körs en gång per testmodul eller testsuits exekveringssession istället för en gång per testfunktion), bygga fixturer som använder andra fixturer, parametrera fixturen (så att fixturen och alla test som använder den fixturen körs flera gånger, en gång för varje parameter som ges till fixturen), fixturer som läser värden från modulen som kallar dem ... som nämnts tidigare har fixturer mycket mer kraft och flexibilitet än en normal installationsfunktion.

Rengöring efter testen är klar.

Låt oss säga att vår kod har vuxit och vårt Stuff-objekt behöver nu speciell sanering.

# 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

Vi kan lägga till en kod för att kalla upp saneringen längst ner i varje testfunktion, men fixturer ger ett bättre sätt att göra detta. Om du lägger till en funktion i fixturen och registrerar den som en finaliserare , kommer koden i finaliseringsfunktionen att ringas efter testet med fixturen har gjort. Om fixturens räckvidd är större än en enda funktion (som modul eller session), kommer finaliseraren att utföras efter att alla tester i omfattning är avslutade, så efter att modulen är klar eller i slutet av hela testkörningssessionen .

@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

Att använda finaliseringsfunktionen i en funktion kan vara lite svårt att förstå vid första anblicken, särskilt när du har mer komplicerade fixturer. Du kan istället använda en avkastningsarmatur för att göra samma sak med ett mänskligt läsbart exekveringsflöde. Den enda verkliga skillnaden är att i stället för att använda return använder vi ett yield vid den del av fixturen där installationen är klar och kontrollen ska gå till en testfunktion, lägg sedan till all saneringskod efter yield . Vi dekorerar det också som ett yield_fixture så att py.test vet hur det ska hanteras.

@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()

Och det avslutar Intro to Test Fixtures!

Mer information finns i den officiella dokumentationen för py.test-fixtur och den officiella dokumentationen för avkastningsarmatur



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow