rename hook, rename addfuncarg to addcall interface,
forget about combinations of funcargs. --HG-- branch : trunk
This commit is contained in:
parent
d9ad2cf761
commit
c3f3dc653e
|
@ -50,21 +50,20 @@ is an example for running the same test function three times.
|
|||
|
||||
.. sourcecode:: python
|
||||
|
||||
def pytest_genfuncruns(runspec):
|
||||
if "arg1" in runspec.funcargnames:
|
||||
runspec.addfuncarg("arg1", 10)
|
||||
runspec.addfuncarg("arg1", 20)
|
||||
runspec.addfuncarg("arg1", 30)
|
||||
def pytest_genfunc(funcspec):
|
||||
if "arg1" in funcspec.funcargnames:
|
||||
for value in range(3):
|
||||
funcspec.addcall(arg1=value)
|
||||
|
||||
def test_function(arg1):
|
||||
assert myfuncarg in (10, 20, 30)
|
||||
assert myfuncarg in (0, 1, 2)
|
||||
|
||||
Here is what happens:
|
||||
|
||||
1. The ``pytest_genfuncruns()`` hook will be called once for each test
|
||||
function. The if-statement makes sure that we only add function
|
||||
arguments (and runs) for functions that need it. The `runspec object`_
|
||||
provides access to context information.
|
||||
1. The ``pytest_genfunc()`` hook will be called once for each test
|
||||
function. The if-statement makes sure that we only add calls
|
||||
for functions that actually need the provided value.
|
||||
The `funcspec object`_ provides access to context information.
|
||||
|
||||
2. Subsequently the ``test_function()`` will be called three times
|
||||
with three different values for ``arg1``.
|
||||
|
@ -91,7 +90,7 @@ Attributes of request objects
|
|||
|
||||
``request.cls``: class object where the test function is defined in or None.
|
||||
|
||||
``runspec.module``: module object where the test function is defined in.
|
||||
``funcspec.module``: module object where the test function is defined in.
|
||||
|
||||
``request.config``: access to command line opts and general config
|
||||
|
||||
|
@ -151,48 +150,31 @@ Test functions can have multiple arguments
|
|||
which can either come from a test generator
|
||||
or from a provider.
|
||||
|
||||
.. _`runspec object`:
|
||||
.. _`funcspec object`:
|
||||
|
||||
runspec objects
|
||||
funcspec objects
|
||||
------------------------
|
||||
|
||||
Runspecs help to inspect a testfunction and
|
||||
to generate tests with combinations of function argument values.
|
||||
|
||||
generating and combining funcargs
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
Calling ``funcspec.addcall(**funcargs)`` will add
|
||||
a tests function call using the given dictionary
|
||||
of function arguments. This addition of a call
|
||||
happens during test collection.
|
||||
|
||||
Calling ``runspec.addfuncarg(argname, value)`` will trigger
|
||||
tests function calls with the given function
|
||||
argument value. For each already existing
|
||||
funcarg combination, the added funcarg value will
|
||||
|
||||
* be merged to the existing funcarg combination if the
|
||||
new argument name isn't part of the funcarg combination yet.
|
||||
|
||||
* otherwise generate a new test call where the existing
|
||||
funcarg combination is copied and updated
|
||||
with the newly added funcarg value.
|
||||
|
||||
For simple usage, e.g. test functions with a single
|
||||
generated function argument, each call to ``addfuncarg``
|
||||
will just trigger a new call.
|
||||
|
||||
This scheme allows two sources to generate
|
||||
function arguments independently from each other.
|
||||
|
||||
Attributes of runspec objects
|
||||
Attributes of funcspec objects
|
||||
++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
``runspec.funcargnames``: set of required function arguments for given function
|
||||
``funcspec.funcargnames``: set of required function arguments for given function
|
||||
|
||||
``runspec.function``: underlying python test function
|
||||
``funcspec.function``: underlying python test function
|
||||
|
||||
``runspec.cls``: class object where the test function is defined in or None.
|
||||
``funcspec.cls``: class object where the test function is defined in or None.
|
||||
|
||||
``runspec.module``: the module object where the test function is defined in.
|
||||
``funcspec.module``: the module object where the test function is defined in.
|
||||
|
||||
``runspec.config``: access to command line opts and general config
|
||||
``funcspec.config``: access to command line opts and general config
|
||||
|
||||
|
||||
Useful Funcarg Tutorial Examples
|
||||
|
@ -386,45 +368,35 @@ methods in a convenient way.
|
|||
|
||||
.. _`py.path.local`: ../path.html#local
|
||||
|
||||
.. _`combine multiple funcarg values`:
|
||||
|
||||
|
||||
parametrize test functions by combining generated funcargs
|
||||
parametrize test functions with multiple func args
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
Adding different funcargs will generate test calls with
|
||||
all combinations of added funcargs. Consider this example:
|
||||
You can trigger calling test functions which take more
|
||||
than one function argument. Consider this example:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def makearg1(runspec):
|
||||
runspec.addfuncarg("arg1", 10)
|
||||
runspec.addfuncarg("arg1", 11)
|
||||
|
||||
def makearg2(runspec):
|
||||
runspec.addfuncarg("arg2", 20)
|
||||
runspec.addfuncarg("arg2", 21)
|
||||
|
||||
def pytest_genfuncruns(runspec):
|
||||
makearg1(runspec)
|
||||
makearg2(runspec)
|
||||
def pytest_genfunc(funcspec):
|
||||
for arg1 in range(2):
|
||||
for arg2 in (10, 20):
|
||||
funcspec.addcall(arg1=arg1, arg2=arg2)
|
||||
|
||||
# the actual test function
|
||||
|
||||
def test_function(arg1, arg2):
|
||||
assert arg1 in (10, 20)
|
||||
assert arg2 in (20, 30)
|
||||
pass
|
||||
|
||||
Running this test module will result in ``test_function``
|
||||
being called four times, in the following order::
|
||||
being called four times, with the following arguments::
|
||||
|
||||
test_function(10, 20)
|
||||
test_function(10, 21)
|
||||
test_function(11, 20)
|
||||
test_function(11, 21)
|
||||
test_function(0, 10)
|
||||
test_function(0, 20)
|
||||
test_function(1, 10)
|
||||
test_function(2, 20)
|
||||
|
||||
|
||||
example: test functions with generated and provided funcargs
|
||||
example: test functions with pre-generated and provided funcargs
|
||||
-------------------------------------------------------------------
|
||||
|
||||
You can mix generated function arguments and normally
|
||||
|
@ -432,10 +404,10 @@ provided ones. Consider this module:
|
|||
|
||||
.. sourcecode:: python
|
||||
|
||||
def pytest_genfuncruns(runspec):
|
||||
if "arg1" in runspec.funcargnames: # test_function2 does not have it
|
||||
runspec.addfuncarg("arg1", 10)
|
||||
runspec.addfuncarg("arg1", 20)
|
||||
def pytest_genfunc(funcspec):
|
||||
if "arg1" in funcspec.funcargnames: # test_function2 does not have it
|
||||
funcspec.addcall(arg1=10)
|
||||
funcspec.addcall(arg1=20)
|
||||
|
||||
def pytest_funcarg__arg2(request):
|
||||
return [10, 20]
|
||||
|
|
|
@ -25,7 +25,7 @@ def fillfuncargs(function):
|
|||
except request.Error:
|
||||
request._raiselookupfailed()
|
||||
|
||||
class RunSpecs:
|
||||
class FuncSpecs:
|
||||
def __init__(self, function, config=None, cls=None, module=None):
|
||||
self.config = config
|
||||
self.module = module
|
||||
|
@ -33,22 +33,14 @@ class RunSpecs:
|
|||
self.funcargnames = getfuncargnames(function)
|
||||
self.cls = cls
|
||||
self.module = module
|
||||
self._combinations = []
|
||||
self._calls = []
|
||||
|
||||
def addfuncarg(self, argname, value):
|
||||
if argname not in self.funcargnames:
|
||||
raise ValueError("function %r has no funcarg %r" %(
|
||||
self.function, argname))
|
||||
newcombi = []
|
||||
if not self._combinations:
|
||||
newcombi.append({argname:value})
|
||||
else:
|
||||
for combi in self._combinations:
|
||||
if argname in combi:
|
||||
combi = combi.copy()
|
||||
newcombi.append(combi)
|
||||
combi[argname] = value
|
||||
self._combinations.extend(newcombi)
|
||||
def addcall(self, **kwargs):
|
||||
for argname in kwargs:
|
||||
if argname not in self.funcargnames:
|
||||
raise ValueError("function %r has no funcarg %r" %(
|
||||
self.function, argname))
|
||||
self._calls.append(kwargs)
|
||||
|
||||
class FunctionCollector(py.test.collect.Collector):
|
||||
def __init__(self, name, parent, combinations):
|
||||
|
|
|
@ -53,7 +53,7 @@ class PluginHooks:
|
|||
""" return custom item/collector for a python object in a module, or None. """
|
||||
pytest_pycollect_obj.firstresult = True
|
||||
|
||||
def pytest_genfuncruns(self, runspec):
|
||||
def pytest_genfunc(self, funcspec):
|
||||
""" generate (multiple) parametrized calls to a test function."""
|
||||
|
||||
def pytest_collectstart(self, collector):
|
||||
|
|
|
@ -172,13 +172,13 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
|
|||
# to work to get at the class
|
||||
clscol = self.getclasscollector()
|
||||
cls = clscol and clscol.obj or None
|
||||
runspec = funcargs.RunSpecs(funcobj, config=self.config, cls=cls, module=module)
|
||||
gentesthook = self.config.hook.pytest_genfuncruns.clone(extralookup=module)
|
||||
gentesthook(runspec=runspec)
|
||||
if not runspec._combinations:
|
||||
funcspec = funcargs.FuncSpecs(funcobj, config=self.config, cls=cls, module=module)
|
||||
gentesthook = self.config.hook.pytest_genfunc.clone(extralookup=module)
|
||||
gentesthook(funcspec=funcspec)
|
||||
if not funcspec._calls:
|
||||
return self.Function(name, parent=self)
|
||||
return funcargs.FunctionCollector(name=name,
|
||||
parent=self, combinations=runspec._combinations)
|
||||
parent=self, combinations=funcspec._calls)
|
||||
|
||||
class Module(py.test.collect.File, PyCollectorMixin):
|
||||
def _getobj(self):
|
||||
|
|
|
@ -131,71 +131,58 @@ class TestRequest:
|
|||
req = funcargs.FuncargRequest(item, "xxx")
|
||||
assert req.fspath == modcol.fspath
|
||||
|
||||
class TestRunSpecs:
|
||||
class TestFuncSpecs:
|
||||
def test_no_funcargs(self, testdir):
|
||||
def function(): pass
|
||||
runspec = funcargs.RunSpecs(function)
|
||||
assert not runspec.funcargnames
|
||||
funcspec = funcargs.FuncSpecs(function)
|
||||
assert not funcspec.funcargnames
|
||||
|
||||
def test_function_basic(self):
|
||||
def func(arg1, arg2="qwe"): pass
|
||||
runspec = funcargs.RunSpecs(func)
|
||||
assert len(runspec.funcargnames) == 1
|
||||
assert 'arg1' in runspec.funcargnames
|
||||
assert runspec.function is func
|
||||
assert runspec.cls is None
|
||||
funcspec = funcargs.FuncSpecs(func)
|
||||
assert len(funcspec.funcargnames) == 1
|
||||
assert 'arg1' in funcspec.funcargnames
|
||||
assert funcspec.function is func
|
||||
assert funcspec.cls is None
|
||||
|
||||
def test_addfuncarg_basic(self):
|
||||
def test_addcall_basic(self):
|
||||
def func(arg1): pass
|
||||
runspec = funcargs.RunSpecs(func)
|
||||
funcspec = funcargs.FuncSpecs(func)
|
||||
py.test.raises(ValueError, """
|
||||
runspec.addfuncarg("notexists", 100)
|
||||
funcspec.addcall(notexists=100)
|
||||
""")
|
||||
runspec.addfuncarg("arg1", 100)
|
||||
assert len(runspec._combinations) == 1
|
||||
assert runspec._combinations[0] == {'arg1': 100}
|
||||
funcspec.addcall(arg1=100)
|
||||
assert len(funcspec._calls) == 1
|
||||
assert funcspec._calls[0] == {'arg1': 100}
|
||||
|
||||
def test_addfuncarg_two(self):
|
||||
def test_addcall_two(self):
|
||||
def func(arg1): pass
|
||||
runspec = funcargs.RunSpecs(func)
|
||||
runspec.addfuncarg("arg1", 100)
|
||||
runspec.addfuncarg("arg1", 101)
|
||||
assert len(runspec._combinations) == 2
|
||||
assert runspec._combinations[0] == {'arg1': 100}
|
||||
assert runspec._combinations[1] == {'arg1': 101}
|
||||
|
||||
def test_addfuncarg_combined(self):
|
||||
runspec = funcargs.RunSpecs(lambda arg1, arg2: 0)
|
||||
runspec.addfuncarg('arg1', 1)
|
||||
runspec.addfuncarg('arg1', 2)
|
||||
runspec.addfuncarg('arg2', 100)
|
||||
combinations = runspec._combinations
|
||||
assert len(combinations) == 2
|
||||
assert combinations[0] == {'arg1': 1, 'arg2': 100}
|
||||
assert combinations[1] == {'arg1': 2, 'arg2': 100}
|
||||
runspec.addfuncarg('arg2', 101)
|
||||
assert len(combinations) == 4
|
||||
assert combinations[-1] == {'arg1': 2, 'arg2': 101}
|
||||
funcspec = funcargs.FuncSpecs(func)
|
||||
funcspec.addcall(arg1=100)
|
||||
funcspec.addcall(arg1=101)
|
||||
assert len(funcspec._calls) == 2
|
||||
assert funcspec._calls[0] == {'arg1': 100}
|
||||
assert funcspec._calls[1] == {'arg1': 101}
|
||||
|
||||
class TestGenfuncFunctional:
|
||||
def test_attributes(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
import py
|
||||
def pytest_genfuncruns(runspec):
|
||||
runspec.addfuncarg("runspec", runspec)
|
||||
def pytest_genfunc(funcspec):
|
||||
funcspec.addcall(funcspec=funcspec)
|
||||
|
||||
def test_function(runspec):
|
||||
assert runspec.config == py.test.config
|
||||
assert runspec.module.__name__ == __name__
|
||||
assert runspec.function == test_function
|
||||
assert runspec.cls is None
|
||||
def test_function(funcspec):
|
||||
assert funcspec.config == py.test.config
|
||||
assert funcspec.module.__name__ == __name__
|
||||
assert funcspec.function == test_function
|
||||
assert funcspec.cls is None
|
||||
class TestClass:
|
||||
def test_method(self, runspec):
|
||||
assert runspec.config == py.test.config
|
||||
assert runspec.module.__name__ == __name__
|
||||
def test_method(self, funcspec):
|
||||
assert funcspec.config == py.test.config
|
||||
assert funcspec.module.__name__ == __name__
|
||||
# XXX actually have the unbound test function here?
|
||||
assert runspec.function == TestClass.test_method.im_func
|
||||
assert runspec.cls == TestClass
|
||||
assert funcspec.function == TestClass.test_method.im_func
|
||||
assert funcspec.cls == TestClass
|
||||
""")
|
||||
result = testdir.runpytest(p, "-v")
|
||||
result.stdout.fnmatch_lines([
|
||||
|
@ -205,10 +192,10 @@ class TestGenfuncFunctional:
|
|||
def test_arg_twice(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
class ConftestPlugin:
|
||||
def pytest_genfuncruns(self, runspec):
|
||||
assert "arg" in runspec.funcargnames
|
||||
runspec.addfuncarg("arg", 10)
|
||||
runspec.addfuncarg("arg", 20)
|
||||
def pytest_genfunc(self, funcspec):
|
||||
assert "arg" in funcspec.funcargnames
|
||||
funcspec.addcall(arg=10)
|
||||
funcspec.addcall(arg=20)
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def test_myfunc(arg):
|
||||
|
@ -223,9 +210,9 @@ class TestGenfuncFunctional:
|
|||
|
||||
def test_two_functions(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_genfuncruns(runspec):
|
||||
runspec.addfuncarg("arg1", 10)
|
||||
runspec.addfuncarg("arg1", 20)
|
||||
def pytest_genfunc(funcspec):
|
||||
funcspec.addcall(arg1=10)
|
||||
funcspec.addcall(arg1=20)
|
||||
|
||||
def test_func1(arg1):
|
||||
assert arg1 == 10
|
||||
|
@ -243,20 +230,17 @@ class TestGenfuncFunctional:
|
|||
def test_genfuncarg_inmodule(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
class ConftestPlugin:
|
||||
def pytest_genfuncruns(self, runspec):
|
||||
assert "arg" in runspec.funcargnames
|
||||
runspec.addfuncarg("arg", 10)
|
||||
def pytest_genfunc(self, funcspec):
|
||||
assert "arg1" in funcspec.funcargnames
|
||||
funcspec.addcall(arg1=1, arg2=2)
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_genfuncruns(runspec):
|
||||
runspec.addfuncarg("arg2", 10)
|
||||
runspec.addfuncarg("arg2", 20)
|
||||
runspec.addfuncarg("classarg", 17)
|
||||
def pytest_genfunc(funcspec):
|
||||
funcspec.addcall(arg1=10, arg2=10)
|
||||
|
||||
class TestClass:
|
||||
def test_myfunc(self, arg, arg2, classarg):
|
||||
assert classarg == 17
|
||||
assert arg == arg2
|
||||
def test_myfunc(self, arg1, arg2):
|
||||
assert arg1 == arg2
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
assert result.stdout.fnmatch_lines([
|
||||
|
|
Loading…
Reference in New Issue