Merge pull request #1734 from nicoddemus/issue-1728-inconsistent-setup-teardown
setup_* and teardown_* functions argument now optional
This commit is contained in:
commit
8a73a2ad60
|
@ -163,6 +163,10 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
automatically generated id for that argument will be used.
|
||||
Thanks `@palaviv`_ for the complete PR (`#1468`_).
|
||||
|
||||
* The parameter to xunit-style setup/teardown methods (``setup_method``,
|
||||
``setup_module``, etc.) is now optional and may be omitted.
|
||||
Thanks `@okken`_ for bringing this to attention and `@nicoddemus`_ for the PR.
|
||||
|
||||
* Improved automatic id generation selection in case of duplicate ids in
|
||||
parametrize.
|
||||
Thanks `@palaviv`_ for the complete PR (`#1474`_).
|
||||
|
@ -312,6 +316,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
.. _@nikratio: https://github.com/nikratio
|
||||
.. _@novas0x2a: https://github.com/novas0x2a
|
||||
.. _@obestwalter: https://github.com/obestwalter
|
||||
.. _@okken: https://github.com/okken
|
||||
.. _@olegpidsadnyi: https://github.com/olegpidsadnyi
|
||||
.. _@omarkohl: https://github.com/omarkohl
|
||||
.. _@palaviv: https://github.com/palaviv
|
||||
|
|
|
@ -25,10 +25,6 @@ cutdir2 = py.path.local(_pytest.__file__).dirpath()
|
|||
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
|
||||
|
||||
|
||||
def _has_positional_arg(func):
|
||||
return func.__code__.co_argcount
|
||||
|
||||
|
||||
def filter_traceback(entry):
|
||||
# entry.path might sometimes return a str object when the entry
|
||||
# points to dynamically generated code
|
||||
|
@ -439,34 +435,51 @@ class Module(pytest.File, PyCollector):
|
|||
"decorator) is not allowed. Use @pytest.mark.skip or "
|
||||
"@pytest.mark.skipif instead."
|
||||
)
|
||||
#print "imported test module", mod
|
||||
self.config.pluginmanager.consider_module(mod)
|
||||
return mod
|
||||
|
||||
def setup(self):
|
||||
setup_module = xunitsetup(self.obj, "setUpModule")
|
||||
setup_module = _get_xunit_setup_teardown(self.obj, "setUpModule")
|
||||
if setup_module is None:
|
||||
setup_module = xunitsetup(self.obj, "setup_module")
|
||||
setup_module = _get_xunit_setup_teardown(self.obj, "setup_module")
|
||||
if setup_module is not None:
|
||||
#XXX: nose compat hack, move to nose plugin
|
||||
# if it takes a positional arg, its probably a pytest style one
|
||||
# so we pass the current module object
|
||||
if _has_positional_arg(setup_module):
|
||||
setup_module(self.obj)
|
||||
else:
|
||||
setup_module()
|
||||
fin = getattr(self.obj, 'tearDownModule', None)
|
||||
if fin is None:
|
||||
fin = getattr(self.obj, 'teardown_module', None)
|
||||
if fin is not None:
|
||||
#XXX: nose compat hack, move to nose plugin
|
||||
# if it takes a positional arg, it's probably a pytest style one
|
||||
# so we pass the current module object
|
||||
if _has_positional_arg(fin):
|
||||
finalizer = lambda: fin(self.obj)
|
||||
else:
|
||||
finalizer = fin
|
||||
self.addfinalizer(finalizer)
|
||||
setup_module()
|
||||
|
||||
teardown_module = _get_xunit_setup_teardown(self.obj, 'tearDownModule')
|
||||
if teardown_module is None:
|
||||
teardown_module = _get_xunit_setup_teardown(self.obj, 'teardown_module')
|
||||
if teardown_module is not None:
|
||||
self.addfinalizer(teardown_module)
|
||||
|
||||
|
||||
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
|
||||
"""
|
||||
Return a callable to perform xunit-style setup or teardown if
|
||||
the function exists in the ``holder`` object.
|
||||
The ``param_obj`` parameter is the parameter which will be passed to the function
|
||||
when the callable is called without arguments, defaults to the ``holder`` object.
|
||||
Return ``None`` if a suitable callable is not found.
|
||||
"""
|
||||
param_obj = param_obj if param_obj is not None else holder
|
||||
result = _get_xunit_func(holder, attr_name)
|
||||
if result is not None:
|
||||
arg_count = result.__code__.co_argcount
|
||||
if inspect.ismethod(result):
|
||||
arg_count -= 1
|
||||
if arg_count:
|
||||
return lambda: result(param_obj)
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
def _get_xunit_func(obj, name):
|
||||
"""Return the attribute from the given object to be used as a setup/teardown
|
||||
xunit-style function, but only if not marked as a fixture to
|
||||
avoid calling it twice.
|
||||
"""
|
||||
meth = getattr(obj, name, None)
|
||||
if fixtures.getfixturemarker(meth) is None:
|
||||
return meth
|
||||
|
||||
|
||||
class Class(PyCollector):
|
||||
|
@ -479,7 +492,7 @@ class Class(PyCollector):
|
|||
return [self._getcustomclass("Instance")(name="()", parent=self)]
|
||||
|
||||
def setup(self):
|
||||
setup_class = xunitsetup(self.obj, 'setup_class')
|
||||
setup_class = _get_xunit_func(self.obj, 'setup_class')
|
||||
if setup_class is not None:
|
||||
setup_class = getattr(setup_class, 'im_func', setup_class)
|
||||
setup_class = getattr(setup_class, '__func__', setup_class)
|
||||
|
@ -523,12 +536,12 @@ class FunctionMixin(PyobjMixin):
|
|||
else:
|
||||
setup_name = 'setup_function'
|
||||
teardown_name = 'teardown_function'
|
||||
setup_func_or_method = xunitsetup(obj, setup_name)
|
||||
setup_func_or_method = _get_xunit_setup_teardown(obj, setup_name, param_obj=self.obj)
|
||||
if setup_func_or_method is not None:
|
||||
setup_func_or_method(self.obj)
|
||||
fin = getattr(obj, teardown_name, None)
|
||||
if fin is not None:
|
||||
self.addfinalizer(lambda: fin(self.obj))
|
||||
setup_func_or_method()
|
||||
teardown_func_or_method = _get_xunit_setup_teardown(obj, teardown_name, param_obj=self.obj)
|
||||
if teardown_func_or_method is not None:
|
||||
self.addfinalizer(teardown_func_or_method)
|
||||
|
||||
def _prunetraceback(self, excinfo):
|
||||
if hasattr(self, '_obj') and not self.config.option.fulltrace:
|
||||
|
@ -1494,11 +1507,3 @@ class Function(FunctionMixin, pytest.Item, fixtures.FuncargnamesCompatAttr):
|
|||
fixtures.fillfixtures(self)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def xunitsetup(obj, name):
|
||||
meth = getattr(obj, name, None)
|
||||
if fixtures.getfixturemarker(meth) is None:
|
||||
return meth
|
||||
|
|
|
@ -7,21 +7,20 @@ classic xunit-style setup
|
|||
|
||||
This section describes a classic and popular way how you can implement
|
||||
fixtures (setup and teardown test state) on a per-module/class/function basis.
|
||||
pytest started supporting these methods around 2005 and subsequently
|
||||
nose and the standard library introduced them (under slightly different
|
||||
names). While these setup/teardown methods are and will remain fully
|
||||
supported you may also use pytest's more powerful :ref:`fixture mechanism
|
||||
<fixture>` which leverages the concept of dependency injection, allowing
|
||||
for a more modular and more scalable approach for managing test state,
|
||||
especially for larger projects and for functional testing. You can
|
||||
mix both fixture mechanisms in the same file but unittest-based
|
||||
test methods cannot receive fixture arguments.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
As of pytest-2.4, teardownX functions are not called if
|
||||
setupX existed and failed/was skipped. This harmonizes
|
||||
behaviour across all major python testing tools.
|
||||
While these setup/teardown methods are simple and familiar to those
|
||||
coming from a ``unittest`` or nose ``background``, you may also consider
|
||||
using pytest's more powerful :ref:`fixture mechanism
|
||||
<fixture>` which leverages the concept of dependency injection, allowing
|
||||
for a more modular and more scalable approach for managing test state,
|
||||
especially for larger projects and for functional testing. You can
|
||||
mix both fixture mechanisms in the same file but
|
||||
test methods of ``unittest.TestCase`` subclasses
|
||||
cannot receive fixture arguments.
|
||||
|
||||
|
||||
Module level setup/teardown
|
||||
--------------------------------------
|
||||
|
@ -38,6 +37,8 @@ which will usually be called once for all the functions::
|
|||
method.
|
||||
"""
|
||||
|
||||
As of pytest-3.0, the ``module`` parameter is optional.
|
||||
|
||||
Class level setup/teardown
|
||||
----------------------------------
|
||||
|
||||
|
@ -71,6 +72,8 @@ Similarly, the following methods are called around each method invocation::
|
|||
call.
|
||||
"""
|
||||
|
||||
As of pytest-3.0, the ``method`` parameter is optional.
|
||||
|
||||
If you would rather define test functions directly at module level
|
||||
you can also use the following functions to implement fixtures::
|
||||
|
||||
|
@ -84,7 +87,13 @@ you can also use the following functions to implement fixtures::
|
|||
call.
|
||||
"""
|
||||
|
||||
Note that it is possible for setup/teardown pairs to be invoked multiple times
|
||||
per testing process.
|
||||
As of pytest-3.0, the ``function`` parameter is optional.
|
||||
|
||||
Remarks:
|
||||
|
||||
* It is possible for setup/teardown pairs to be invoked multiple times
|
||||
per testing process.
|
||||
* teardown functions are not called if the corresponding setup function existed
|
||||
and failed/was skipped.
|
||||
|
||||
.. _`unittest.py module`: http://docs.python.org/library/unittest.html
|
||||
|
|
|
@ -229,11 +229,12 @@ class BaseFunctionalTests:
|
|||
assert reps[5].failed
|
||||
|
||||
def test_exact_teardown_issue1206(self, testdir):
|
||||
"""issue shadowing error with wrong number of arguments on teardown_method."""
|
||||
rec = testdir.inline_runsource("""
|
||||
import pytest
|
||||
|
||||
class TestClass:
|
||||
def teardown_method(self):
|
||||
def teardown_method(self, x, y, z):
|
||||
pass
|
||||
|
||||
def test_method(self):
|
||||
|
@ -256,9 +257,9 @@ class BaseFunctionalTests:
|
|||
assert reps[2].when == "teardown"
|
||||
assert reps[2].longrepr.reprcrash.message in (
|
||||
# python3 error
|
||||
'TypeError: teardown_method() takes 1 positional argument but 2 were given',
|
||||
"TypeError: teardown_method() missing 2 required positional arguments: 'y' and 'z'",
|
||||
# python2 error
|
||||
'TypeError: teardown_method() takes exactly 1 argument (2 given)'
|
||||
'TypeError: teardown_method() takes exactly 4 arguments (2 given)'
|
||||
)
|
||||
|
||||
def test_failure_in_setup_function_ignores_custom_repr(self, testdir):
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#
|
||||
# test correct setup/teardowns at
|
||||
# module, class, and instance level
|
||||
import pytest
|
||||
|
||||
|
||||
def test_module_and_function_setup(testdir):
|
||||
reprec = testdir.inline_runsource("""
|
||||
|
@ -251,3 +253,53 @@ def test_setup_funcarg_setup_when_outer_scope_fails(testdir):
|
|||
"*2 error*"
|
||||
])
|
||||
assert "xyz43" not in result.stdout.str()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('arg', ['', 'arg'])
|
||||
def test_setup_teardown_function_level_with_optional_argument(testdir, monkeypatch, arg):
|
||||
"""parameter to setup/teardown xunit-style functions parameter is now optional (#1728)."""
|
||||
import sys
|
||||
trace_setups_teardowns = []
|
||||
monkeypatch.setattr(sys, 'trace_setups_teardowns', trace_setups_teardowns, raising=False)
|
||||
p = testdir.makepyfile("""
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
trace = sys.trace_setups_teardowns.append
|
||||
|
||||
def setup_module({arg}): trace('setup_module')
|
||||
def teardown_module({arg}): trace('teardown_module')
|
||||
|
||||
def setup_function({arg}): trace('setup_function')
|
||||
def teardown_function({arg}): trace('teardown_function')
|
||||
|
||||
def test_function_1(): pass
|
||||
def test_function_2(): pass
|
||||
|
||||
class Test:
|
||||
def setup_method(self, {arg}): trace('setup_method')
|
||||
def teardown_method(self, {arg}): trace('teardown_method')
|
||||
|
||||
def test_method_1(self): pass
|
||||
def test_method_2(self): pass
|
||||
""".format(arg=arg))
|
||||
result = testdir.inline_run(p)
|
||||
result.assertoutcome(passed=4)
|
||||
|
||||
expected = [
|
||||
'setup_module',
|
||||
|
||||
'setup_function',
|
||||
'teardown_function',
|
||||
'setup_function',
|
||||
'teardown_function',
|
||||
|
||||
'setup_method',
|
||||
'teardown_method',
|
||||
|
||||
'setup_method',
|
||||
'teardown_method',
|
||||
|
||||
'teardown_module',
|
||||
]
|
||||
assert trace_setups_teardowns == expected
|
||||
|
|
Loading…
Reference in New Issue