addcall now takes direct funcargs values alternatively
--HG-- branch : trunk
This commit is contained in:
parent
65a04bc3be
commit
19c9506fa3
|
@ -22,12 +22,12 @@ more "templaty" and more test-aspect oriented. In fact,
|
|||
funcarg mechanisms are meant to be complete and
|
||||
convenient enough to
|
||||
|
||||
* substitute most usages of `xUnit style`_ setup.
|
||||
* substitute and improve on most usages of `xUnit style`_ setup.
|
||||
For a simple example of how funcargs compare
|
||||
to xUnit setup, see the `blog post about
|
||||
the monkeypatch funcarg`_.
|
||||
|
||||
* substitute all usages of `old-style generative tests`_,
|
||||
* substitute and improve on all usages of `old-style generative tests`_,
|
||||
i.e. test functions that use the "yield" statement.
|
||||
Using yield in test functions is deprecated since 1.0.
|
||||
|
||||
|
@ -95,7 +95,7 @@ Note that if you misspell a function argument or want
|
|||
to use one that isn't available, an error with a list of
|
||||
available function argument is provided.
|
||||
|
||||
For provider functions that make good use of the
|
||||
For more interesting provider functions that make good use of the
|
||||
`request object`_ please see the `application setup tutorial example`_.
|
||||
|
||||
.. _`request object`:
|
||||
|
@ -164,9 +164,9 @@ for a use of this method.
|
|||
generating parametrized tests with funcargs
|
||||
===========================================================
|
||||
|
||||
You can parametrize multiple runs of the same test function
|
||||
by schedulings new test function calls which get different
|
||||
funcarg values. Let's look at a simple self-contained
|
||||
You can directly parametrize multiple runs of the same test
|
||||
function by adding new test function calls with different
|
||||
function argument values. Let's look at a simple self-contained
|
||||
example:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
@ -175,10 +175,7 @@ example:
|
|||
def pytest_generate_tests(metafunc):
|
||||
if "numiter" in metafunc.funcargnames:
|
||||
for i in range(10):
|
||||
metafunc.addcall(param=i)
|
||||
|
||||
def pytest_funcarg__numiter(request):
|
||||
return request.param
|
||||
metafunc.addcall(funcargs=dict(numiter=i))
|
||||
|
||||
def test_func(numiter):
|
||||
assert numiter < 9
|
||||
|
@ -208,16 +205,9 @@ If you run this with ``py.test test_example.py`` you'll get:
|
|||
Here is what happens in detail:
|
||||
|
||||
1. ``pytest_generate_tests(metafunc)`` hook is called once for each test
|
||||
function. ``metafunc.addcall(param=i)`` adds new test function calls
|
||||
where the ``param`` will appear as ``request.param``.
|
||||
function. It adds ten new function calls with explicit function arguments.
|
||||
|
||||
2. the ``pytest_funcarg__arg1(request)`` provider
|
||||
is called 10 times. Each time it receives a request object
|
||||
that has a ``request.param`` as previously provided by the generator.
|
||||
Our provider here simply passes through the ``param`` value.
|
||||
We could also setup more heavyweight resources here.
|
||||
|
||||
3. **execute tests**: ``test_func(numiter)`` is called ten times with
|
||||
2. **execute tests**: ``test_func(numiter)`` is called ten times with
|
||||
ten different arguments.
|
||||
|
||||
.. _`metafunc object`:
|
||||
|
@ -246,11 +236,11 @@ the ``metafunc.addcall()`` method
|
|||
|
||||
.. sourcecode:: python
|
||||
|
||||
def addcall(id=None, param=None):
|
||||
""" trigger a later test function call. """
|
||||
def addcall(funcargs={}, id=None, param=None):
|
||||
""" trigger a new test function call. """
|
||||
|
||||
The specified ``param`` will be seen by the
|
||||
`funcarg provider`_ as a ``request.param`` attribute.
|
||||
``funcargs`` can be a dictionary of argument names
|
||||
mapped to values - providing it is called *direct parametrization*.
|
||||
|
||||
If you provide an `id`` it will be used for reporting
|
||||
and identification purposes. If you don't supply an `id`
|
||||
|
@ -258,11 +248,16 @@ the stringified counter of the list of added calls will be used.
|
|||
``id`` values needs to be unique between all
|
||||
invocations for a given test function.
|
||||
|
||||
*Test generators are called during test collection which
|
||||
is separate from the actual test setup and test run.
|
||||
With distributed testing setting up funcargs will
|
||||
even happen in a different process. Therefore one should
|
||||
defer setup of heavyweight objects to funcarg providers.*
|
||||
``param`` if specified will be seen by any
|
||||
`funcarg provider`_ as a ``request.param`` attribute.
|
||||
Setting it is called *indirect parametrization*.
|
||||
|
||||
Indirect parametrization is preferable if test values are
|
||||
expensive to setup or can only be created in certain environments.
|
||||
Test generators and thus ``addcall()`` invocations are performed
|
||||
during test collection which is separate from the actual test
|
||||
setup and test run phase. With distributed testing collection
|
||||
and test setup/run happens in different process.
|
||||
|
||||
|
||||
.. _`tutorial examples`:
|
||||
|
|
|
@ -4,22 +4,24 @@ from py.__.test.dist.mypickle import ImmutablePickler, PickleChannel, UnpickleEr
|
|||
|
||||
class A:
|
||||
pass
|
||||
def test_pickle_and_back_IS_same():
|
||||
def pickle_band_back_IS_same(obj, proto):
|
||||
p1 = ImmutablePickler(uneven=False, protocol=proto)
|
||||
p2 = ImmutablePickler(uneven=True, protocol=proto)
|
||||
s1 = p1.dumps(obj)
|
||||
d2 = p2.loads(s1)
|
||||
s2 = p2.dumps(d2)
|
||||
obj_back = p1.loads(s2)
|
||||
assert obj is obj_back
|
||||
|
||||
a1 = A()
|
||||
a2 = A()
|
||||
a2.a1 = a1
|
||||
for proto in (0,1,2, -1):
|
||||
for obj in {1:2}, [1,2,3], a1, a2:
|
||||
yield pickle_band_back_IS_same, obj, proto
|
||||
def pytest_generate_tests(metafunc):
|
||||
if "obj" in metafunc.funcargnames and "proto" in metafunc.funcargnames:
|
||||
a1 = A()
|
||||
a2 = A()
|
||||
a2.a1 = a1
|
||||
for proto in (0,1,2, -1):
|
||||
for obj in {1:2}, [1,2,3], a1, a2:
|
||||
metafunc.addcall(funcargs=dict(obj=obj, proto=proto))
|
||||
|
||||
def test_pickle_and_back_IS_same(obj, proto):
|
||||
p1 = ImmutablePickler(uneven=False, protocol=proto)
|
||||
p2 = ImmutablePickler(uneven=True, protocol=proto)
|
||||
s1 = p1.dumps(obj)
|
||||
d2 = p2.loads(s1)
|
||||
s2 = p2.dumps(d2)
|
||||
obj_back = p1.loads(s2)
|
||||
assert obj is obj_back
|
||||
|
||||
def test_pickling_twice_before_unpickling():
|
||||
p1 = ImmutablePickler(uneven=False)
|
||||
|
|
|
@ -24,7 +24,8 @@ def fillfuncargs(function):
|
|||
|
||||
_notexists = object()
|
||||
class CallSpec:
|
||||
def __init__(self, id, param):
|
||||
def __init__(self, funcargs, id, param):
|
||||
self.funcargs = funcargs
|
||||
self.id = id
|
||||
if param is not _notexists:
|
||||
self.param = param
|
||||
|
@ -40,14 +41,15 @@ class Metafunc:
|
|||
self._calls = []
|
||||
self._ids = py.builtin.set()
|
||||
|
||||
def addcall(self, id=None, param=_notexists):
|
||||
def addcall(self, funcargs=None, id=None, param=_notexists):
|
||||
assert funcargs is None or isinstance(funcargs, dict)
|
||||
if id is None:
|
||||
id = len(self._calls)
|
||||
id = str(id)
|
||||
if id in self._ids:
|
||||
raise ValueError("duplicate id %r" % id)
|
||||
self._ids.add(id)
|
||||
self._calls.append(CallSpec(id, param))
|
||||
self._calls.append(CallSpec(funcargs, id, param))
|
||||
|
||||
class FunctionCollector(py.test.collect.Collector):
|
||||
def __init__(self, name, parent, calls):
|
||||
|
@ -57,9 +59,10 @@ class FunctionCollector(py.test.collect.Collector):
|
|||
|
||||
def collect(self):
|
||||
l = []
|
||||
for call in self.calls:
|
||||
function = self.parent.Function(name="%s[%s]" %(self.name, call.id),
|
||||
parent=self, requestparam=call.param, callobj=self.obj)
|
||||
for callspec in self.calls:
|
||||
name = "%s[%s]" %(self.name, callspec.id)
|
||||
function = self.parent.Function(name=name, parent=self,
|
||||
callspec=callspec, callobj=self.obj)
|
||||
l.append(function)
|
||||
return l
|
||||
|
||||
|
|
|
@ -326,14 +326,16 @@ class Function(FunctionMixin, py.test.collect.Item):
|
|||
and executing a Python callable test object.
|
||||
"""
|
||||
def __init__(self, name, parent=None, config=None, args=(),
|
||||
requestparam=_dummy, callobj=_dummy):
|
||||
callspec=None, callobj=_dummy):
|
||||
super(Function, self).__init__(name, parent, config=config)
|
||||
self._finalizers = []
|
||||
self._args = args
|
||||
if not args: # yielded functions (deprecated) have positional args
|
||||
self.funcargs = {}
|
||||
if requestparam is not _dummy:
|
||||
self._requestparam = requestparam
|
||||
if args:
|
||||
assert not callspec, "yielded functions (deprecated) cannot have funcargs"
|
||||
else:
|
||||
self.funcargs = callspec and callspec.funcargs or {}
|
||||
if hasattr(callspec, "param"):
|
||||
self._requestparam = callspec.param
|
||||
if callobj is not _dummy:
|
||||
self._obj = callobj
|
||||
|
||||
|
|
|
@ -177,6 +177,16 @@ class TestMetafunc:
|
|||
assert metafunc._calls[1].param == obj
|
||||
assert metafunc._calls[2].param == 1
|
||||
|
||||
def test_addcall_funcargs(self):
|
||||
def func(arg1): pass
|
||||
metafunc = funcargs.Metafunc(func)
|
||||
class obj: pass
|
||||
metafunc.addcall(funcargs={"x": 2})
|
||||
metafunc.addcall(funcargs={"x": 3})
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == {'x': 2}
|
||||
assert metafunc._calls[1].funcargs == {'x': 3}
|
||||
assert not hasattr(metafunc._calls[1], 'param')
|
||||
|
||||
class TestGenfuncFunctional:
|
||||
def test_attributes(self, testdir):
|
||||
|
@ -208,6 +218,28 @@ class TestGenfuncFunctional:
|
|||
"*2 passed in*",
|
||||
])
|
||||
|
||||
def test_addcall_with_funcargs_two(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
class ConftestPlugin:
|
||||
def pytest_generate_tests(self, metafunc):
|
||||
assert "arg1" in metafunc.funcargnames
|
||||
metafunc.addcall(funcargs=dict(arg1=1, arg2=2))
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(funcargs=dict(arg1=1, arg2=1))
|
||||
|
||||
class TestClass:
|
||||
def test_myfunc(self, arg1, arg2):
|
||||
assert arg1 == arg2
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
assert result.stdout.fnmatch_lines([
|
||||
"*test_myfunc*0*PASS*",
|
||||
"*test_myfunc*1*FAIL*",
|
||||
"*1 failed, 1 passed*"
|
||||
])
|
||||
|
||||
def test_two_functions(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
|
|
Loading…
Reference in New Issue