From afe847ecdc96060874e591d1c713b27ee09dee62 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 20 Jun 2017 23:43:34 -0300 Subject: [PATCH] fixture docs: highlight difference between yield and addfinalizer methods Fix #2508 --- doc/en/fixture.rst | 80 +++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index f760c423e..f305cb2c2 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -123,20 +123,13 @@ with a list of available function arguments. but is not anymore advertised as the primary means of declaring fixture 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 -term "funcargs" or "funcarg mechanism" which continues to be present -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 -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 +Fixtures 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 *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 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:: Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one @@ -303,29 +299,49 @@ the ``with`` statement ends. 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 +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 - import pytest +Here's the ``smtp`` fixture changed to use ``addfinalizer`` for cleanup: - @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 +.. code-block:: python - 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 similar 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. - 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`: @@ -782,8 +798,8 @@ Autouse fixtures (xUnit setup on steroids) .. regendoc:wipe Occasionally, you may want to have fixtures get invoked automatically -without a `usefixtures`_ or `funcargs`_ reference. As a practical -example, suppose we have a database fixture which has a +without declaring a function argument explicitly or a `usefixtures`_ decorator. +As a practical example, suppose we have a database fixture which has a begin/rollback/commit architecture and we want to automatically surround each test method by a transaction and a rollback. Here is a dummy self-contained implementation of this idea::