addcall now takes direct funcargs values alternatively

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-05-13 03:01:02 +02:00
parent 65a04bc3be
commit 19c9506fa3
5 changed files with 88 additions and 54 deletions

View File

@ -22,12 +22,12 @@ more "templaty" and more test-aspect oriented. In fact,
funcarg mechanisms are meant to be complete and funcarg mechanisms are meant to be complete and
convenient enough to 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 For a simple example of how funcargs compare
to xUnit setup, see the `blog post about to xUnit setup, see the `blog post about
the monkeypatch funcarg`_. 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. i.e. test functions that use the "yield" statement.
Using yield in test functions is deprecated since 1.0. 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 to use one that isn't available, an error with a list of
available function argument is provided. 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`_ please see the `application setup tutorial example`_.
.. _`request object`: .. _`request object`:
@ -164,9 +164,9 @@ for a use of this method.
generating parametrized tests with funcargs generating parametrized tests with funcargs
=========================================================== ===========================================================
You can parametrize multiple runs of the same test function You can directly parametrize multiple runs of the same test
by schedulings new test function calls which get different function by adding new test function calls with different
funcarg values. Let's look at a simple self-contained function argument values. Let's look at a simple self-contained
example: example:
.. sourcecode:: python .. sourcecode:: python
@ -175,10 +175,7 @@ example:
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
if "numiter" in metafunc.funcargnames: if "numiter" in metafunc.funcargnames:
for i in range(10): for i in range(10):
metafunc.addcall(param=i) metafunc.addcall(funcargs=dict(numiter=i))
def pytest_funcarg__numiter(request):
return request.param
def test_func(numiter): def test_func(numiter):
assert numiter < 9 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: Here is what happens in detail:
1. ``pytest_generate_tests(metafunc)`` hook is called once for each test 1. ``pytest_generate_tests(metafunc)`` hook is called once for each test
function. ``metafunc.addcall(param=i)`` adds new test function calls function. It adds ten new function calls with explicit function arguments.
where the ``param`` will appear as ``request.param``.
2. the ``pytest_funcarg__arg1(request)`` provider 2. **execute tests**: ``test_func(numiter)`` is called ten times with
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
ten different arguments. ten different arguments.
.. _`metafunc object`: .. _`metafunc object`:
@ -246,11 +236,11 @@ the ``metafunc.addcall()`` method
.. sourcecode:: python .. sourcecode:: python
def addcall(id=None, param=None): def addcall(funcargs={}, id=None, param=None):
""" trigger a later test function call. """ """ trigger a new test function call. """
The specified ``param`` will be seen by the ``funcargs`` can be a dictionary of argument names
`funcarg provider`_ as a ``request.param`` attribute. mapped to values - providing it is called *direct parametrization*.
If you provide an `id`` it will be used for reporting If you provide an `id`` it will be used for reporting
and identification purposes. If you don't supply an `id` 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 ``id`` values needs to be unique between all
invocations for a given test function. invocations for a given test function.
*Test generators are called during test collection which ``param`` if specified will be seen by any
is separate from the actual test setup and test run. `funcarg provider`_ as a ``request.param`` attribute.
With distributed testing setting up funcargs will Setting it is called *indirect parametrization*.
even happen in a different process. Therefore one should
defer setup of heavyweight objects to funcarg providers.* 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`: .. _`tutorial examples`:

View File

@ -4,22 +4,24 @@ from py.__.test.dist.mypickle import ImmutablePickler, PickleChannel, UnpickleEr
class A: class A:
pass 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() def pytest_generate_tests(metafunc):
a2 = A() if "obj" in metafunc.funcargnames and "proto" in metafunc.funcargnames:
a2.a1 = a1 a1 = A()
for proto in (0,1,2, -1): a2 = A()
for obj in {1:2}, [1,2,3], a1, a2: a2.a1 = a1
yield pickle_band_back_IS_same, obj, proto 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(): def test_pickling_twice_before_unpickling():
p1 = ImmutablePickler(uneven=False) p1 = ImmutablePickler(uneven=False)

View File

@ -24,7 +24,8 @@ def fillfuncargs(function):
_notexists = object() _notexists = object()
class CallSpec: class CallSpec:
def __init__(self, id, param): def __init__(self, funcargs, id, param):
self.funcargs = funcargs
self.id = id self.id = id
if param is not _notexists: if param is not _notexists:
self.param = param self.param = param
@ -40,14 +41,15 @@ class Metafunc:
self._calls = [] self._calls = []
self._ids = py.builtin.set() 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: if id is None:
id = len(self._calls) id = len(self._calls)
id = str(id) id = str(id)
if id in self._ids: if id in self._ids:
raise ValueError("duplicate id %r" % id) raise ValueError("duplicate id %r" % id)
self._ids.add(id) self._ids.add(id)
self._calls.append(CallSpec(id, param)) self._calls.append(CallSpec(funcargs, id, param))
class FunctionCollector(py.test.collect.Collector): class FunctionCollector(py.test.collect.Collector):
def __init__(self, name, parent, calls): def __init__(self, name, parent, calls):
@ -57,9 +59,10 @@ class FunctionCollector(py.test.collect.Collector):
def collect(self): def collect(self):
l = [] l = []
for call in self.calls: for callspec in self.calls:
function = self.parent.Function(name="%s[%s]" %(self.name, call.id), name = "%s[%s]" %(self.name, callspec.id)
parent=self, requestparam=call.param, callobj=self.obj) function = self.parent.Function(name=name, parent=self,
callspec=callspec, callobj=self.obj)
l.append(function) l.append(function)
return l return l

View File

@ -326,14 +326,16 @@ class Function(FunctionMixin, py.test.collect.Item):
and executing a Python callable test object. and executing a Python callable test object.
""" """
def __init__(self, name, parent=None, config=None, args=(), 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) super(Function, self).__init__(name, parent, config=config)
self._finalizers = [] self._finalizers = []
self._args = args self._args = args
if not args: # yielded functions (deprecated) have positional args if args:
self.funcargs = {} assert not callspec, "yielded functions (deprecated) cannot have funcargs"
if requestparam is not _dummy: else:
self._requestparam = requestparam self.funcargs = callspec and callspec.funcargs or {}
if hasattr(callspec, "param"):
self._requestparam = callspec.param
if callobj is not _dummy: if callobj is not _dummy:
self._obj = callobj self._obj = callobj

View File

@ -177,6 +177,16 @@ class TestMetafunc:
assert metafunc._calls[1].param == obj assert metafunc._calls[1].param == obj
assert metafunc._calls[2].param == 1 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: class TestGenfuncFunctional:
def test_attributes(self, testdir): def test_attributes(self, testdir):
@ -208,6 +218,28 @@ class TestGenfuncFunctional:
"*2 passed in*", "*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): def test_two_functions(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):