Merge pull request #2511 from nicoddemus/addfinalizer-docs

fixture docs: highlight difference between yield and addfinalizer methods
This commit is contained in:
Ronny Pfannschmidt 2017-06-22 07:59:44 +02:00 committed by GitHub
commit 4a62102b57
1 changed files with 89 additions and 71 deletions

View File

@ -123,20 +123,13 @@ with a list of available function arguments.
but is not anymore advertised as the primary means of declaring fixture but is not anymore advertised as the primary means of declaring fixture
functions. functions.
"Funcargs" a prime example of dependency injection Fixtures: a prime example of dependency injection
--------------------------------------------------- ---------------------------------------------------
When injecting fixtures to test functions, pytest-2.0 introduced the Fixtures allow test functions to easily receive and work
term "funcargs" or "funcarg mechanism" which continues to be present against specific pre-initialized application objects without having
also in docs today. It now refers to the specific case of injecting to care about import/setup/cleanup details.
fixture values as arguments to test functions. With pytest-2.3 there are It's a prime example of `dependency injection`_ where fixture
more possibilities to use fixtures but "funcargs" remain as the main way
as they allow to directly state the dependencies of a test function.
As the following examples show in more detail, funcargs allow test
functions to easily receive and work against specific pre-initialized
application objects without having to care about import/setup/cleanup
details. It's a prime example of `dependency injection`_ where fixture
functions take the role of the *injector* and test functions are the functions take the role of the *injector* and test functions are the
*consumers* of fixture objects. *consumers* of fixture objects.
@ -296,6 +289,9 @@ The ``smtp`` connection will be closed after the test finished execution
because the ``smtp`` object automatically closes when because the ``smtp`` object automatically closes when
the ``with`` statement ends. the ``with`` statement ends.
Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
*teardown* code (after the ``yield``) will not be called.
.. note:: .. note::
Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one
@ -303,29 +299,51 @@ the ``with`` statement ends.
fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed
and considered deprecated. 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 An alternative option for executing *teardown* code is to
make use of the ``addfinalizer`` method of the `request-context`_ object to register
finalization functions.
import smtplib Here's the ``smtp`` fixture changed to use ``addfinalizer`` for cleanup:
import pytest
@pytest.fixture(scope="module") .. code-block:: python
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 in the module has finished execution. # 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
Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
ends, but ``addfinalizer`` has two key differences over ``yield``:
1. It is possible to register multiple finalizer functions.
2. Finalizers will always be called regardless if the fixture *setup* code raises an exception.
This is handy to properly close all resources created by a fixture even if one of them
fails to be created/acquired::
@pytest.fixture
def equipments(request):
r = []
for port in ('C1', 'C3', 'C28'):
equip = connect(port)
request.addfinalizer(equip.disconnect)
r.append(equip)
return r
In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
be properly closed. Of course, if an exception happens before the finalize function is
registered then it will not be executed.
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`:
@ -782,8 +800,8 @@ Autouse fixtures (xUnit setup on steroids)
.. regendoc:wipe .. regendoc:wipe
Occasionally, you may want to have fixtures get invoked automatically Occasionally, you may want to have fixtures get invoked automatically
without a `usefixtures`_ or `funcargs`_ reference. As a practical without declaring a function argument explicitly or a `usefixtures`_ decorator.
example, suppose we have a database fixture which has a As a practical example, suppose we have a database fixture which has a
begin/rollback/commit architecture and we want to automatically surround begin/rollback/commit architecture and we want to automatically surround
each test method by a transaction and a rollback. Here is a dummy each test method by a transaction and a rollback. Here is a dummy
self-contained implementation of this idea:: self-contained implementation of this idea::