document context fixtures, also improve plugin docs
This commit is contained in:
parent
9d8645b45d
commit
150ad0172f
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in New Issue