Python Language
py.test
Zoeken…
Py.test instellen
py.test
is een van de verschillende py.test
van derden die beschikbaar zijn voor Python. Het kan worden geïnstalleerd met behulp van pip
with
pip install pytest
De te testen code
Stel dat we een projectroot/module/code.py
in projectroot/module/code.py
:
# projectroot/module/code.py
def add(a, b):
return a + b
De testcode
We maken een testbestand in projectroot/tests/test_code.py
. Het bestand moet beginnen met test_
om te worden herkend als een test_
.
# projectroot/tests/test_code.py
from module import code
def test_add():
assert code.add(1, 2) == 3
De test uitvoeren
Vanuit projectroot
we eenvoudig 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 ================================================
Niet-uitgevoerde tests
Een mislukte test levert nuttige resultaten op over wat er mis is gegaan:
# projectroot/tests/test_code.py
from module import code
def test_add__failing():
assert code.add(10, 11) == 33
resultaten:
$ 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 ================================================
Inleiding tot het testen van armaturen
Bij meer gecompliceerde tests moeten soms dingen zijn ingesteld voordat u de code uitvoert die u wilt testen. Het is mogelijk om dit in de testfunctie zelf te doen, maar dan eindigen er grote testfuncties die zo veel doen dat het moeilijk is om te zeggen waar de installatie stopt en de test begint. U kunt ook veel dubbele installatiecode krijgen tussen uw verschillende testfuncties.
Ons codebestand:
# projectroot/module/stuff.py
class Stuff(object):
def prep(self):
self.foo = 1
self.bar = 2
Ons testbestand:
# 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
Dit zijn vrij eenvoudige voorbeelden, maar als ons Stuff
object veel meer configuratie nodig had, zou het log worden. We zien dat er een dubbele code tussen onze testgevallen zit, dus laten we dat eerst in een afzonderlijke functie veranderen.
# 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
Dit ziet er beter uit, maar we hebben nog steeds de my_stuff = get_prepped_stuff()
aanroep die onze testfuncties my_stuff = get_prepped_stuff()
.
py.test armaturen te hulp!
Armaturen zijn veel krachtigere en flexibelere versies van testinstellingen. Ze kunnen veel meer dan we hier doen, maar we doen het stap voor stap.
Eerst veranderen we get_prepped_stuff
in een fixture genaamd prepped_stuff
. Je wilt je fixtures een naam geven met zelfstandige naamwoorden in plaats van werkwoorden vanwege de manier waarop de fixtures later in de testfuncties zelf worden gebruikt. Het @pytest.fixture
geeft aan dat deze specifieke functie als een fixture moet worden behandeld in plaats van als een normale functie.
@pytest.fixture
def prepped_stuff():
my_stuff = stuff.Stuff()
my_stuff.prep()
return my_stuff
Nu moeten we de testfuncties bijwerken zodat ze het armatuur gebruiken. Dit wordt gedaan door een parameter aan hun definitie toe te voegen die exact overeenkomt met de naam van het toestel. Wanneer py.test wordt uitgevoerd, zal het de fixture uitvoeren voordat de test wordt uitgevoerd en vervolgens de retourwaarde van de fixture via die parameter in de testfunctie doorgeven. (Houd er rekening mee dat fixtures geen waarde moeten retourneren; ze kunnen in plaats daarvan andere setup-dingen doen, zoals het aanroepen van een externe bron, het regelen van dingen op het bestandssysteem, het plaatsen van waarden in een database, wat de tests ook nodig hebben voor de setup)
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 kun je zien waarom we het een zelfstandig naamwoord hebben gegeven. maar de my_stuff = prepped_stuff
is vrijwel nutteloos, dus laten we in plaats daarvan gewoon prepped_stuff
gebruiken.
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 gebruiken we armaturen! We kunnen verder gaan door de reikwijdte van de fixture te wijzigen (zodat deze slechts één keer per testmodule of testsuite wordt uitgevoerd in plaats van één keer per testfunctie), fixtures bouwen die andere fixtures gebruiken, de fixture parametriseren (zodat de fixture en alle tests met die fixture worden meerdere keren uitgevoerd, eenmaal voor elke parameter die aan de fixture wordt gegeven), fixtures die waarden lezen van de module die ze oproept ... zoals eerder vermeld, hebben fixtures veel meer kracht en flexibiliteit dan een normale setup-functie.
Opruimen nadat de tests zijn voltooid.
Laten we zeggen dat onze code is gegroeid en ons Stuff-object nu moet worden opgeschoond.
# 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
We kunnen wat code toevoegen om het opschonen onderaan elke testfunctie aan te roepen, maar armaturen bieden een betere manier om dit te doen. Als u een functie aan de fixture toevoegt en deze registreert als finalizer , wordt de code in de finalizerfunctie opgeroepen nadat de test met de fixture is voltooid. Als de scope van de fixture groter is dan een enkele functie (zoals module of sessie), wordt de finalizer uitgevoerd nadat alle tests in scope zijn voltooid, dus nadat de module is uitgevoerd of aan het einde van de hele testrunsessie .
@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
Het gebruik van de finalizer-functie in een functie kan op het eerste gezicht wat moeilijk te begrijpen zijn, vooral als u meer gecompliceerde armaturen hebt. U kunt in plaats daarvan een opbrengstvoorziening gebruiken om hetzelfde te doen met een meer leesbare uitvoeringsstroom. Het enige echte verschil is dat we in plaats van return
een yield
op het gedeelte van de fixture waar de installatie is voltooid en de besturing naar een testfunctie moet gaan en vervolgens alle opruimcode na de yield
. We decoreren het ook als een yield_fixture
zodat py.test weet hoe het moet worden verwerkt.
@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()
En daarmee is de inleiding tot het testen van armaturen afgesloten!
Voor meer informatie, zie de officiële py.test fixture documentatie en de officiële yield fixture documentatie