Update docs in prol of using yield statements

This commit is contained in:
Bruno Oliveira 2016-06-07 20:59:58 -03:00
parent 98acda426f
commit fe4f23c1bf
4 changed files with 86 additions and 54 deletions

View File

@ -4,8 +4,8 @@ import pytest
@pytest.fixture("session") @pytest.fixture("session")
def setup(request): def setup(request):
setup = CostlySetup() setup = CostlySetup()
request.addfinalizer(setup.finalize) yield setup
return setup setup.finalize()
class CostlySetup: class CostlySetup:
def __init__(self): def __init__(self):

View File

@ -648,15 +648,14 @@ here is a little example implemented via a local plugin::
@pytest.fixture @pytest.fixture
def something(request): def something(request):
def fin(): yield
# request.node is an "item" because we use the default # request.node is an "item" because we use the default
# "function" scope # "function" scope
if request.node.rep_setup.failed: if request.node.rep_setup.failed:
print ("setting up a test failed!", request.node.nodeid) print ("setting up a test failed!", request.node.nodeid)
elif request.node.rep_setup.passed: elif request.node.rep_setup.passed:
if request.node.rep_call.failed: if request.node.rep_call.failed:
print ("executing test failed", request.node.nodeid) print ("executing test failed", request.node.nodeid)
request.addfinalizer(fin)
if you then have failing tests:: if you then have failing tests::

View File

@ -34,11 +34,6 @@ both styles, moving incrementally from classic to new style, as you
prefer. You can also start out from existing :ref:`unittest.TestCase prefer. You can also start out from existing :ref:`unittest.TestCase
style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects. style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
.. note::
pytest-2.4 introduced an additional :ref:`yield fixture mechanism
<yieldfixture>` for easier context manager integration and more linear
writing of teardown code.
.. _`funcargs`: .. _`funcargs`:
.. _`funcarg mechanism`: .. _`funcarg mechanism`:
@ -247,9 +242,8 @@ Fixture finalization / executing teardown code
------------------------------------------------------------- -------------------------------------------------------------
pytest supports execution of fixture specific finalization code pytest supports execution of fixture specific finalization code
when the fixture goes out of scope. By accepting a ``request`` object when the fixture goes out of scope. By using a ``yield`` statement instead of ``return``, all
into your fixture function you can call its ``request.addfinalizer`` one the code after the *yield* statement serves as the teardown code.::
or multiple times::
# content of conftest.py # content of conftest.py
@ -259,14 +253,12 @@ or multiple times::
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def smtp(request): def smtp(request):
smtp = smtplib.SMTP("smtp.gmail.com") smtp = smtplib.SMTP("smtp.gmail.com")
def fin(): yield smtp # provide the fixture value
print ("teardown smtp") print("teardown smtp")
smtp.close() smtp.close()
request.addfinalizer(fin)
return smtp # provide the fixture value
The ``fin`` function will execute when the last test using The ``print`` and ``smtp.close()`` statements will execute when the last test using
the fixture in the module has finished execution. the fixture in the module has finished execution, regardless of the exception status of the tests.
Let's execute it:: Let's execute it::
@ -282,14 +274,55 @@ occur around each single test. In either case the test
module itself does not need to change or know about these details module itself does not need to change or know about these details
of fixture setup. of fixture setup.
Note that we can also seamlessly use the ``yield`` syntax with ``with`` statements::
Finalization/teardown with yield fixtures # content of test_yield2.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Another alternative to the *request.addfinalizer()* method is to use *yield import pytest
fixtures*. All the code after the *yield* statement serves as the teardown
code. See the :ref:`yield fixture documentation <yieldfixture>`.
@pytest.fixture
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::
Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one
had to mark a fixture using the ``yield_fixture`` marker. From 2.10 onward, normal
fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed
and considered deprecated.
.. note::
As historical note, another way to write teardown code is
by accepting a ``request`` object into your fixture function and can call its
``request.addfinalizer`` one or multiple times::
# content of conftest.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp(request):
smtp = smtplib.SMTP("smtp.gmail.com")
def fin():
print ("teardown smtp")
smtp.close()
request.addfinalizer(fin)
return smtp # provide the fixture value
The ``fin`` function will execute when the last test using
the fixture in the module has finished execution.
This method is still fully supported, but ``yield`` is recommended from 2.10 onward because
it is considered simpler and better describes the natural code flow.
.. _`request-context`: .. _`request-context`:
@ -309,12 +342,9 @@ read an optional server URL from the test module which uses our fixture::
def smtp(request): def smtp(request):
server = getattr(request.module, "smtpserver", "smtp.gmail.com") server = getattr(request.module, "smtpserver", "smtp.gmail.com")
smtp = smtplib.SMTP(server) smtp = smtplib.SMTP(server)
yield smtp
def fin(): print ("finalizing %s (%s)" % (smtp, server))
print ("finalizing %s (%s)" % (smtp, server)) smtp.close()
smtp.close()
request.addfinalizer(fin)
return smtp
We use the ``request.module`` attribute to optionally obtain an We use the ``request.module`` attribute to optionally obtain an
``smtpserver`` attribute from the test module. If we just execute ``smtpserver`` attribute from the test module. If we just execute
@ -351,7 +381,7 @@ from the module namespace.
.. _`fixture-parametrize`: .. _`fixture-parametrize`:
Parametrizing a fixture Parametrizing fixtures
----------------------------------------------------------------- -----------------------------------------------------------------
Fixture functions can be parametrized in which case they will be called Fixture functions can be parametrized in which case they will be called
@ -374,11 +404,9 @@ through the special :py:class:`request <FixtureRequest>` object::
params=["smtp.gmail.com", "mail.python.org"]) params=["smtp.gmail.com", "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
@ -586,19 +614,15 @@ to show the setup/teardown flow::
def modarg(request): def modarg(request):
param = request.param param = request.param
print (" SETUP modarg %s" % param) print (" SETUP modarg %s" % param)
def fin(): yield param
print (" TEARDOWN modarg %s" % param) print (" TEARDOWN modarg %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):
param = request.param param = request.param
print (" SETUP otherarg %s" % param) print (" SETUP otherarg %s" % param)
def fin(): yield param
print (" TEARDOWN otherarg %s" % param) print (" TEARDOWN otherarg %s" % param)
request.addfinalizer(fin)
return param
def test_0(otherarg): def test_0(otherarg):
print (" RUN test0 with otherarg %s" % otherarg) print (" RUN test0 with otherarg %s" % otherarg)
@ -777,7 +801,8 @@ self-contained implementation of this idea::
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def transact(self, request, db): def transact(self, request, db):
db.begin(request.function.__name__) db.begin(request.function.__name__)
request.addfinalizer(db.rollback) yield
db.rollback()
def test_method1(self, db): def test_method1(self, db):
assert db.intransaction == ["test_method1"] assert db.intransaction == ["test_method1"]
@ -817,10 +842,11 @@ active. The canonical way to do that is to put the transact definition
into a conftest.py file **without** using ``autouse``:: into a conftest.py file **without** using ``autouse``::
# content of conftest.py # content of conftest.py
@pytest.fixture() @pytest.fixture
def transact(self, request, db): def transact(self, request, db):
db.begin() db.begin()
request.addfinalizer(db.rollback) yield
db.rollback()
and then e.g. have a TestClass using it by declaring the need:: and then e.g. have a TestClass using it by declaring the need::

View File

@ -3,8 +3,15 @@
Fixture functions using "yield" / context manager integration Fixture functions using "yield" / context manager integration
--------------------------------------------------------------- ---------------------------------------------------------------
.. deprecated:: 2.10
.. versionadded:: 2.4 .. versionadded:: 2.4
.. important::
Since pytest-2.10, fixtures using the normal ``fixture`` decorator can use a ``yield``
statement to provide fixture values and execute teardown code, exactly like ``yield_fixture``
described in this session.
.. regendoc:wipe .. regendoc:wipe
pytest-2.4 allows fixture functions to seamlessly use a ``yield`` instead pytest-2.4 allows fixture functions to seamlessly use a ``yield`` instead