strike keyword argument in favour of new pytest.yield_fixture decorator

This commit is contained in:
holger krekel 2013-09-30 13:42:39 +02:00
parent 431ce79d94
commit 086d4e4ced
5 changed files with 44 additions and 29 deletions

View File

@ -16,9 +16,12 @@ known incompatibilities:
new features: new features:
- experimentally allow fixture functions to "yield" instead of "return" - experimentally introduce a new pytest.yield_fixture decorator which
a fixture value, allowing direct integration with with-context managers has exactly the same parameters as pytest.fixture but expects
in fixture functions and avoiding registration of finalization callbacks. a ``yield`` statement instead of a ``return statement`` from fixture functions.
This allows direct integration with with-context managers
in fixture functions and generally avoids registering of finalization callbacks
in favour of treating the "after-yield" as teardown code.
Thanks Andreas Pelme, Vladimir Keleshev, Floris Bruynooghe, Ronny Pfannschmidt Thanks Andreas Pelme, Vladimir Keleshev, Floris Bruynooghe, Ronny Pfannschmidt
and many others for discussions. and many others for discussions.

View File

@ -40,7 +40,7 @@ class FixtureFunctionMarker:
return function return function
def fixture(scope="function", params=None, autouse=False, yieldctx=False): def fixture(scope="function", params=None, autouse=False):
""" (return a) decorator to mark a fixture factory function. """ (return a) decorator to mark a fixture factory function.
This decorator can be used (with or or without parameters) to define This decorator can be used (with or or without parameters) to define
@ -62,16 +62,29 @@ def fixture(scope="function", params=None, autouse=False, yieldctx=False):
can see it. If False (the default) then an explicit can see it. If False (the default) then an explicit
reference is needed to activate the fixture. reference is needed to activate the fixture.
:arg yieldctx: if True, the fixture function yields a fixture value.
Code after such a ``yield`` statement is treated as
teardown code.
""" """
if callable(scope) and params is None and autouse == False: if callable(scope) and params is None and autouse == False:
# direct decoration # direct decoration
return FixtureFunctionMarker( return FixtureFunctionMarker(
"function", params, autouse, yieldctx)(scope) "function", params, autouse)(scope)
else: else:
return FixtureFunctionMarker(scope, params, autouse, yieldctx) return FixtureFunctionMarker(scope, params, autouse)
def yield_fixture(scope="function", params=None, autouse=False):
""" (return a) decorator to mark a yield-fixture factory function
(EXPERIMENTAL).
This takes the same arguments as :py:func:`pytest.fixture` but
expects a fixture function to use a ``yield`` instead of a ``return``
statement to provide a fixture. See
http://pytest.org/en/latest/yieldfixture.html for more info.
"""
if callable(scope) and params is None and autouse == False:
# direct decoration
return FixtureFunctionMarker(
"function", params, autouse, yieldctx=True)(scope)
else:
return FixtureFunctionMarker(scope, params, autouse, yieldctx=True)
defaultfuncargprefixmarker = fixture() defaultfuncargprefixmarker = fixture()
@ -136,6 +149,7 @@ def pytest_namespace():
raises.Exception = pytest.fail.Exception raises.Exception = pytest.fail.Exception
return { return {
'fixture': fixture, 'fixture': fixture,
'yield_fixture': yield_fixture,
'raises' : raises, 'raises' : raises,
'collect': { 'collect': {
'Module': Module, 'Class': Class, 'Instance': Instance, 'Module': Module, 'Class': Class, 'Instance': Instance,
@ -1675,7 +1689,7 @@ def call_fixture_func(fixturefunc, request, kwargs, yieldctx):
if yieldctx: if yieldctx:
if not is_generator(fixturefunc): if not is_generator(fixturefunc):
fail_fixturefunc(fixturefunc, fail_fixturefunc(fixturefunc,
msg="yieldctx=True requires yield statement") msg="yield_fixture requires yield statement in function")
iter = fixturefunc(**kwargs) iter = fixturefunc(**kwargs)
next = getattr(iter, "__next__", None) next = getattr(iter, "__next__", None)
if next is None: if next is None:
@ -1688,7 +1702,7 @@ def call_fixture_func(fixturefunc, request, kwargs, yieldctx):
pass pass
else: else:
fail_fixturefunc(fixturefunc, fail_fixturefunc(fixturefunc,
"fixture function has more than one 'yield'") "yield_fixture function has more than one 'yield'")
request.addfinalizer(teardown) request.addfinalizer(teardown)
else: else:
res = fixturefunc(**kwargs) res = fixturefunc(**kwargs)

View File

@ -276,8 +276,8 @@ module itself does not need to change or know about these details
of fixture setup. of fixture setup.
Note that pytest-2.4 introduced an experimental alternative Note that pytest-2.4 introduced an experimental alternative
:ref:`yield fixture mechanism <yieldctx>` for easier context manager integration :ref:`yield fixture mechanism <yieldfixture>` for easier context manager
and more linear writing of teardown code. integration and more linear writing of teardown code.
.. _`request-context`: .. _`request-context`:

View File

@ -1,5 +1,5 @@
.. _yieldctx: .. _yieldfixture:
Fixture functions using "yield" / context manager integration Fixture functions using "yield" / context manager integration
--------------------------------------------------------------- ---------------------------------------------------------------
@ -16,10 +16,9 @@ fully supporting all other fixture features.
"yielding" fixture values is an experimental feature and its exact "yielding" fixture values is an experimental feature and its exact
declaration may change later but earliest in a 2.5 release. You can thus 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 safely use this feature in the 2.4 series but may need to adapt later.
fixtures later. Test functions themselves will not need to change Test functions themselves will not need to change (as a general
(they can be completely ignorant of the return/yield modes of feature, they are ignorant of how fixtures are setup).
fixture functions).
Let's look at a simple standalone-example using the new ``yield`` syntax:: Let's look at a simple standalone-example using the new ``yield`` syntax::
@ -27,7 +26,7 @@ Let's look at a simple standalone-example using the new ``yield`` syntax::
import pytest import pytest
@pytest.fixture(yieldctx=True) @pytest.yield_fixture
def passwd(): def passwd():
print ("\nsetup before yield") print ("\nsetup before yield")
f = open("/etc/passwd") f = open("/etc/passwd")
@ -62,7 +61,7 @@ Let's simplify the above ``passwd`` fixture::
import pytest import pytest
@pytest.fixture(yieldctx=True) @pytest.yield_fixture
def passwd(): def passwd():
with open("/etc/passwd") as f: with open("/etc/passwd") as f:
yield f.readlines() yield f.readlines()

View File

@ -1980,7 +1980,7 @@ class TestContextManagerFixtureFuncs:
def test_simple(self, testdir): def test_simple(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.fixture(yieldctx=True) @pytest.yield_fixture
def arg1(): def arg1():
print ("setup") print ("setup")
yield 1 yield 1
@ -2004,7 +2004,7 @@ class TestContextManagerFixtureFuncs:
def test_scoped(self, testdir): def test_scoped(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.fixture(scope="module", yieldctx=True) @pytest.yield_fixture(scope="module")
def arg1(): def arg1():
print ("setup") print ("setup")
yield 1 yield 1
@ -2025,7 +2025,7 @@ class TestContextManagerFixtureFuncs:
def test_setup_exception(self, testdir): def test_setup_exception(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.fixture(scope="module", yieldctx=True) @pytest.yield_fixture(scope="module")
def arg1(): def arg1():
pytest.fail("setup") pytest.fail("setup")
yield 1 yield 1
@ -2041,7 +2041,7 @@ class TestContextManagerFixtureFuncs:
def test_teardown_exception(self, testdir): def test_teardown_exception(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.fixture(scope="module", yieldctx=True) @pytest.yield_fixture(scope="module")
def arg1(): def arg1():
yield 1 yield 1
pytest.fail("teardown") pytest.fail("teardown")
@ -2057,7 +2057,7 @@ class TestContextManagerFixtureFuncs:
def test_yields_more_than_one(self, testdir): def test_yields_more_than_one(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.fixture(scope="module", yieldctx=True) @pytest.yield_fixture(scope="module")
def arg1(): def arg1():
yield 1 yield 1
yield 2 yield 2
@ -2074,7 +2074,7 @@ class TestContextManagerFixtureFuncs:
def test_no_yield(self, testdir): def test_no_yield(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.fixture(scope="module", yieldctx=True) @pytest.yield_fixture(scope="module")
def arg1(): def arg1():
return 1 return 1
def test_1(arg1): def test_1(arg1):
@ -2082,9 +2082,8 @@ class TestContextManagerFixtureFuncs:
""") """)
result = testdir.runpytest("-s") result = testdir.runpytest("-s")
result.stdout.fnmatch_lines(""" result.stdout.fnmatch_lines("""
*yieldctx*requires*yield* *yield_fixture*requires*yield*
*yieldctx=True* *yield_fixture*
*def arg1* *def arg1*
""") """)