document context fixtures, also improve plugin docs

This commit is contained in:
holger krekel 2013-05-07 21:37:08 +02:00
parent 9d8645b45d
commit 150ad0172f
2 changed files with 109 additions and 44 deletions

View File

@ -3,7 +3,7 @@ Changes between 2.3.5 and 2.4.DEV
- (experimental) allow fixture functions to be - (experimental) allow fixture functions to be
implemented as context managers. Thanks Andreas Pelme, implemented as context managers. Thanks Andreas Pelme,
Vladimir Keleshev. Vladimir Keleshev.
- (experimental) allow boolean expression directly with skipif/xfail - (experimental) allow boolean expression directly with skipif/xfail
if a "reason" is also specified. Rework skipping documentation if a "reason" is also specified. Rework skipping documentation

View File

@ -7,14 +7,14 @@ pytest fixtures: explicit, modular, scalable
.. currentmodule:: _pytest.python .. currentmodule:: _pytest.python
.. versionadded:: 2.0/2.3 .. versionadded:: 2.0/2.3/2.4
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit .. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
.. _`general purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software .. _`purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection#Definition .. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection#Definition
The `general purpose of test fixtures`_ is to provide a fixed baseline The `purpose of test fixtures`_ is to provide a fixed baseline
upon which tests can reliably and repeatedly execute. pytest-2.3 fixtures upon which tests can reliably and repeatedly execute. pytest fixtures
offer dramatic improvements over the classic xUnit style of setup/teardown offer dramatic improvements over the classic xUnit style of setup/teardown
functions: functions:
@ -22,8 +22,7 @@ functions:
from test functions, modules, classes or whole projects. from test functions, modules, classes or whole projects.
* fixtures are implemented in a modular manner, as each fixture name * fixtures are implemented in a modular manner, as each fixture name
triggers a *fixture function* which can itself easily use other triggers a *fixture function* which can itself use other fixtures.
fixtures.
* fixture management scales from simple unit to complex * fixture management scales from simple unit to complex
functional testing, allowing to parametrize fixtures and tests according functional testing, allowing to parametrize fixtures and tests according
@ -129,10 +128,10 @@ Funcargs a prime example of dependency injection
When injecting fixtures to test functions, pytest-2.0 introduced the When injecting fixtures to test functions, pytest-2.0 introduced the
term "funcargs" or "funcarg mechanism" which continues to be present term "funcargs" or "funcarg mechanism" which continues to be present
also in pytest-2.3 docs. It now refers to the specific case of injecting also in docs today. It now refers to the specific case of injecting
fixture values as arguments to test functions. With pytest-2.3 there are fixture values as arguments to test functions. With pytest-2.3 there are
more possibilities to use fixtures but "funcargs" probably will remain more possibilities to use fixtures but "funcargs" remain as the main way
as the main way of dealing with fixtures. as they allow to directly state the dependencies of a test function.
As the following examples show in more detail, funcargs allow test As the following examples show in more detail, funcargs allow test
functions to easily receive and work against specific pre-initialized functions to easily receive and work against specific pre-initialized
@ -154,10 +153,10 @@ can add a ``scope='module'`` parameter to the
:py:func:`@pytest.fixture <_pytest.python.fixture>` invocation :py:func:`@pytest.fixture <_pytest.python.fixture>` invocation
to cause the decorated ``smtp`` fixture function to only be invoked once to cause the decorated ``smtp`` fixture function to only be invoked once
per test module. Multiple test functions in a test module will thus per test module. Multiple test functions in a test module will thus
each receive the same ``smtp`` fixture instance. The next example also each receive the same ``smtp`` fixture instance. The next example puts
extracts the fixture function into a separate ``conftest.py`` file so the fixture function into a separate ``conftest.py`` file so
that all tests in test modules in the directory can access the fixture that tests from multiple test modules in the directory can
function:: access the fixture function::
# content of conftest.py # content of conftest.py
import pytest import pytest
@ -233,24 +232,91 @@ instance, you can simply declare it::
def smtp(...): def smtp(...):
# the returned fixture value will be shared for # the returned fixture value will be shared for
# all tests needing it # all tests needing it
.. _`contextfixtures`:
fixture finalization / teardowns
-------------------------------------------------------------
pytest supports two styles of fixture finalization:
- (new in pytest-2.4) by writing a contextmanager fixture
generator where a fixture value is "yielded" and the remainder
of the function serves as the teardown code. This integrates
very well with existing context managers.
- by making a fixture function accept a ``request`` argument
with which it can call ``request.addfinalizer(teardownfunction)``
to register a teardown callback function.
Both methods are strictly equivalent from pytest's view and will
remain supported in the future.
Because a number of people prefer the new contextmanager style
we describe it first::
# content of test_ctxfixture.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp():
smtp = smtplib.SMTP("merlinux.eu")
yield smtp # provide the fixture value
print ("teardown smtp")
smtp.close()
pytest detects that you are using a ``yield`` in your fixture function,
turns it into a generator and:
a) iterates once into it for producing the value
b) iterates a second time for tearing the fixture down, expecting
a StopIteration (which is produced automatically from the Python
runtime when the generator returns).
.. note::
The teardown will execute independently of the status of test functions.
You do not need to write the teardown code into a ``try-finally`` clause
like you would usually do with ``contextlib.contextmanager`` decorated
functions.
If the fixture generator yields a second value pytest will report
an error. Yielding cannot be used for parametrization. We'll describe
ways to implement parametrization further below.
Prior to pytest-2.4 you always needed to register a finalizer by accepting
a ``request`` object into your fixture function and calling
``request.addfinalizer`` with a teardown function::
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp(request):
smtp = smtplib.SMTP("merlinux.eu")
def fin():
print ("teardown smtp")
smtp.close()
return smtp # provide the fixture value
This method of registering a finalizer reads more indirect
than the new contextmanager style syntax because ``fin``
is a callback function.
.. _`request-context`: .. _`request-context`:
Fixtures can interact with the requesting test context Fixtures can interact with the requesting test context
------------------------------------------------------------- -------------------------------------------------------------
Fixture functions can themselves use other fixtures by naming pytest provides a builtin :py:class:`request <FixtureRequest>` object,
them as an input argument just like test functions do, see
:ref:`interdependent fixtures`. Moreover, pytest
provides a builtin :py:class:`request <FixtureRequest>` object,
which fixture functions can use to introspect the function, class or module which fixture functions can use to introspect the function, class or module
for which they are invoked or to register finalizing (cleanup) for which they are invoked.
functions which are called when the last test finished execution.
Further extending the previous ``smtp`` fixture example, let's Further extending the previous ``smtp`` fixture example, let's
read an optional server URL from the module namespace and register read an optional server URL from the module namespace::
a finalizer that closes the smtp connection after the last
test in a module finished execution::
# content of conftest.py # content of conftest.py
import pytest import pytest
@ -260,26 +326,25 @@ test in a module finished execution::
def smtp(request): def smtp(request):
server = getattr(request.module, "smtpserver", "merlinux.eu") server = getattr(request.module, "smtpserver", "merlinux.eu")
smtp = smtplib.SMTP(server) smtp = smtplib.SMTP(server)
def fin(): yield smtp # provide the fixture
print ("finalizing %s" % smtp) print ("finalizing %s" % smtp)
smtp.close() smtp.close()
request.addfinalizer(fin)
return smtp
The registered ``fin`` function will be called when the last test The finalizing part after the ``yield smtp`` statement will execute
using it has executed:: when the last test using the ``smtp`` fixture has executed::
$ py.test -s -q --tb=no $ py.test -s -q --tb=no
FF FF
finalizing <smtplib.SMTP instance at 0x1e10248> finalizing <smtplib.SMTP instance at 0x1e10248>
We see that the ``smtp`` instance is finalized after the two We see that the ``smtp`` instance is finalized after the two
tests using it tests executed. If we had specified ``scope='function'`` tests which use it finished executin. If we rather specify
then fixture setup and cleanup would occur around each single test. ``scope='function'`` then fixture setup and cleanup occurs
Note that either case the test module itself does not need to change! around each single test. Note that in either case the test
module itself does not need to change!
Let's quickly create another test module that actually sets the Let's quickly create another test module that actually sets the
server URL and has a test to verify the fixture picks it up:: server URL in its module namespace::
# content of test_anothersmtp.py # content of test_anothersmtp.py
@ -298,6 +363,10 @@ Running it::
> assert 0, smtp.helo() > assert 0, smtp.helo()
E AssertionError: (250, 'mail.python.org') E AssertionError: (250, 'mail.python.org')
voila! The ``smtp`` fixture function picked up our mail server name
from the module namespace.
.. _`fixture-parametrize`: .. _`fixture-parametrize`:
Parametrizing a fixture Parametrizing a fixture
@ -323,11 +392,9 @@ through the special :py:class:`request <FixtureRequest>` object::
params=["merlinux.eu", "mail.python.org"]) params=["merlinux.eu", "mail.python.org"])
def smtp(request): def smtp(request):
smtp = smtplib.SMTP(request.param) smtp = smtplib.SMTP(request.param)
def fin(): yield smtp
print ("finalizing %s" % smtp) print ("finalizing %s" % smtp)
smtp.close() smtp.close()
request.addfinalizer(fin)
return smtp
The main change is the declaration of ``params`` with The main change is the declaration of ``params`` with
:py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values :py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values
@ -467,10 +534,8 @@ to show the setup/teardown flow::
def modarg(request): def modarg(request):
param = request.param param = request.param
print "create", param print "create", param
def fin(): yield param
print "fin", param print ("fin %s" % param)
request.addfinalizer(fin)
return param
@pytest.fixture(scope="function", params=[1,2]) @pytest.fixture(scope="function", params=[1,2])
def otherarg(request): def otherarg(request):
@ -517,8 +582,8 @@ You can see that the parametrized module-scoped ``modarg`` resource caused
an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed
before the ``mod2`` resource was setup. before the ``mod2`` resource was setup.
.. _`usefixtures`:
.. _`usefixtures`:
using fixtures from classes, modules or projects using fixtures from classes, modules or projects
---------------------------------------------------------------------- ----------------------------------------------------------------------