133 lines
4.8 KiB
Plaintext
133 lines
4.8 KiB
Plaintext
|
|
||
|
.. _yieldctx:
|
||
|
|
||
|
Fixture functions using "yield" / context manager integration
|
||
|
---------------------------------------------------------------
|
||
|
|
||
|
.. versionadded:: 2.4
|
||
|
|
||
|
.. regendoc:wipe
|
||
|
|
||
|
pytest-2.4 allows fixture functions to seemlessly use a ``yield`` instead
|
||
|
of a ``return`` statement to provide a fixture value while otherwise
|
||
|
fully supporting all other fixture features.
|
||
|
|
||
|
.. note::
|
||
|
|
||
|
"yielding" fixture values is an experimental feature and its exact
|
||
|
declaration may change later but earliest in a 2.5 release. You can thus
|
||
|
safely use this feature in the 2.4 series but may need to adapt your
|
||
|
fixtures later. Test functions themselves will not need to change
|
||
|
(they can be completely ignorant of the return/yield modes of
|
||
|
fixture functions).
|
||
|
|
||
|
Let's look at a simple standalone-example using the new ``yield`` syntax::
|
||
|
|
||
|
# content of test_yield.py
|
||
|
|
||
|
import pytest
|
||
|
|
||
|
@pytest.fixture(yieldctx=True)
|
||
|
def passwd():
|
||
|
print ("\nsetup before yield")
|
||
|
f = open("/etc/passwd")
|
||
|
yield f.readlines()
|
||
|
print ("teardown after yield")
|
||
|
f.close()
|
||
|
|
||
|
def test_has_lines(passwd):
|
||
|
print ("test called")
|
||
|
assert passwd
|
||
|
|
||
|
In contrast to :ref:`finalization through registering callbacks
|
||
|
<finalization>`, our fixture function used a ``yield``
|
||
|
statement to provide the lines of the ``/etc/passwd`` file.
|
||
|
The code after the ``yield`` statement serves as the teardown code,
|
||
|
avoiding the indirection of registering a teardown callback function.
|
||
|
|
||
|
Let's run it with output capturing disabled::
|
||
|
|
||
|
$ py.test -q -s test_yield.py
|
||
|
|
||
|
setup before yield
|
||
|
test called
|
||
|
.teardown after yield
|
||
|
|
||
|
1 passed in 0.01 seconds
|
||
|
|
||
|
We can also seemlessly use the new syntax with ``with`` statements.
|
||
|
Let's simplify the above ``passwd`` fixture::
|
||
|
|
||
|
# content of test_yield2.py
|
||
|
|
||
|
import pytest
|
||
|
|
||
|
@pytest.fixture(yieldctx=True)
|
||
|
def passwd():
|
||
|
with open("/etc/passwd") as f:
|
||
|
yield f.readlines()
|
||
|
|
||
|
def test_has_lines(passwd):
|
||
|
assert len(passwd) >= 1
|
||
|
|
||
|
The file ``f`` will be closed after the test finished execution
|
||
|
because the Python ``file`` object supports finalization when
|
||
|
the ``with`` statement ends.
|
||
|
|
||
|
Note that the new syntax is fully integrated with using ``scope``,
|
||
|
``params`` and other fixture features. Changing existing
|
||
|
fixture functions to use ``yield`` is thus straight forward.
|
||
|
|
||
|
Discussion and future considerations / feedback
|
||
|
++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
|
|
||
|
The yield-syntax has been discussed by pytest users extensively.
|
||
|
In general, the advantages of the using a ``yield`` fixture syntax are:
|
||
|
|
||
|
- easy provision of fixtures in conjunction with context managers.
|
||
|
|
||
|
- no need to register a callback, providing for more synchronous
|
||
|
control flow in the fixture function. Also there is no need to accept
|
||
|
the ``request`` object into the fixture function just for providing
|
||
|
finalization code.
|
||
|
|
||
|
However, there are also limitations or foreseeable irritations:
|
||
|
|
||
|
- usually ``yield`` is typically used for producing multiple values.
|
||
|
But fixture functions can only yield exactly one value.
|
||
|
Yielding a second fixture value will get you an error.
|
||
|
It's possible we can evolve pytest to allow for producing
|
||
|
multiple values as an alternative to current parametrization.
|
||
|
For now, you can just use the normal
|
||
|
:ref:`fixture parametrization <fixture-parametrize>`
|
||
|
mechanisms together with ``yield``-style fixtures.
|
||
|
|
||
|
- the ``yield`` syntax is similar to what
|
||
|
:py:func:`contextlib.contextmanager` decorated functions
|
||
|
provide. With pytest fixture functions, the "after yield" part will
|
||
|
always be invoked, independently from the exception status
|
||
|
of the test function which uses the fixture. The pytest
|
||
|
behaviour makes sense if you consider that many different
|
||
|
test functions might use a module or session scoped fixture.
|
||
|
Some test functions might raise exceptions and others not,
|
||
|
so how could pytest re-raise a single exception at the
|
||
|
``yield`` point in the fixture function?
|
||
|
|
||
|
- lastly ``yield`` introduces more than one way to write
|
||
|
fixture functions, so what's the obvious way to a newcomer?
|
||
|
Newcomers reading the docs will see feature examples using the
|
||
|
``return`` style so should use that, if in doubt.
|
||
|
Others can start experimenting with writing yield-style fixtures
|
||
|
and possibly help evolving them further.
|
||
|
|
||
|
Some developers also expressed their preference for
|
||
|
rather introduce a new ``@pytest.yieldfixture`` decorator
|
||
|
instead of a keyword argument, or for assuming the above
|
||
|
yield-semantics automatically by introspecting if a fixture
|
||
|
function is a generator. Depending on more experiences and
|
||
|
feedback during the 2.4 cycle, we revisit theses issues.
|
||
|
|
||
|
If you want to feedback or participate in the ongoing
|
||
|
discussion, please join our :ref:`contact channels`.
|
||
|
you are most welcome.
|