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
|
.. sourcecode:: python
|
||||||
|
|
||||||
def pytest_genfuncruns(runspec):
|
def pytest_genfunc(funcspec):
|
||||||
if "arg1" in runspec.funcargnames:
|
if "arg1" in funcspec.funcargnames:
|
||||||
runspec.addfuncarg("arg1", 10)
|
for value in range(3):
|
||||||
runspec.addfuncarg("arg1", 20)
|
funcspec.addcall(arg1=value)
|
||||||
runspec.addfuncarg("arg1", 30)
|
|
||||||
|
|
||||||
def test_function(arg1):
|
def test_function(arg1):
|
||||||
assert myfuncarg in (10, 20, 30)
|
assert myfuncarg in (0, 1, 2)
|
||||||
|
|
||||||
Here is what happens:
|
Here is what happens:
|
||||||
|
|
||||||
1. The ``pytest_genfuncruns()`` hook will be called once for each test
|
1. The ``pytest_genfunc()`` hook will be called once for each test
|
||||||
function. The if-statement makes sure that we only add function
|
function. The if-statement makes sure that we only add calls
|
||||||
arguments (and runs) for functions that need it. The `runspec object`_
|
for functions that actually need the provided value.
|
||||||
provides access to context information.
|
The `funcspec object`_ provides access to context information.
|
||||||
|
|
||||||
2. Subsequently the ``test_function()`` will be called three times
|
2. Subsequently the ``test_function()`` will be called three times
|
||||||
with three different values for ``arg1``.
|
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.
|
``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
|
``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
|
which can either come from a test generator
|
||||||
or from a provider.
|
or from a provider.
|
||||||
|
|
||||||
.. _`runspec object`:
|
.. _`funcspec object`:
|
||||||
|
|
||||||
runspec objects
|
funcspec objects
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
Runspecs help to inspect a testfunction and
|
Runspecs help to inspect a testfunction and
|
||||||
to generate tests with combinations of function argument values.
|
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
|
Attributes of funcspec objects
|
||||||
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
|
|
||||||
++++++++++++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
``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
|
Useful Funcarg Tutorial Examples
|
||||||
|
@ -386,45 +368,35 @@ methods in a convenient way.
|
||||||
|
|
||||||
.. _`py.path.local`: ../path.html#local
|
.. _`py.path.local`: ../path.html#local
|
||||||
|
|
||||||
.. _`combine multiple funcarg values`:
|
|
||||||
|
|
||||||
|
parametrize test functions with multiple func args
|
||||||
parametrize test functions by combining generated funcargs
|
|
||||||
--------------------------------------------------------------------------
|
--------------------------------------------------------------------------
|
||||||
|
|
||||||
Adding different funcargs will generate test calls with
|
You can trigger calling test functions which take more
|
||||||
all combinations of added funcargs. Consider this example:
|
than one function argument. Consider this example:
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
def makearg1(runspec):
|
def pytest_genfunc(funcspec):
|
||||||
runspec.addfuncarg("arg1", 10)
|
for arg1 in range(2):
|
||||||
runspec.addfuncarg("arg1", 11)
|
for arg2 in (10, 20):
|
||||||
|
funcspec.addcall(arg1=arg1, arg2=arg2)
|
||||||
def makearg2(runspec):
|
|
||||||
runspec.addfuncarg("arg2", 20)
|
|
||||||
runspec.addfuncarg("arg2", 21)
|
|
||||||
|
|
||||||
def pytest_genfuncruns(runspec):
|
|
||||||
makearg1(runspec)
|
|
||||||
makearg2(runspec)
|
|
||||||
|
|
||||||
# the actual test function
|
# the actual test function
|
||||||
|
|
||||||
def test_function(arg1, arg2):
|
def test_function(arg1, arg2):
|
||||||
assert arg1 in (10, 20)
|
pass
|
||||||
assert arg2 in (20, 30)
|
|
||||||
|
|
||||||
Running this test module will result in ``test_function``
|
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(0, 10)
|
||||||
test_function(10, 21)
|
test_function(0, 20)
|
||||||
test_function(11, 20)
|
test_function(1, 10)
|
||||||
test_function(11, 21)
|
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
|
You can mix generated function arguments and normally
|
||||||
|
@ -432,10 +404,10 @@ provided ones. Consider this module:
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
def pytest_genfuncruns(runspec):
|
def pytest_genfunc(funcspec):
|
||||||
if "arg1" in runspec.funcargnames: # test_function2 does not have it
|
if "arg1" in funcspec.funcargnames: # test_function2 does not have it
|
||||||
runspec.addfuncarg("arg1", 10)
|
funcspec.addcall(arg1=10)
|
||||||
runspec.addfuncarg("arg1", 20)
|
funcspec.addcall(arg1=20)
|
||||||
|
|
||||||
def pytest_funcarg__arg2(request):
|
def pytest_funcarg__arg2(request):
|
||||||
return [10, 20]
|
return [10, 20]
|
||||||
|
|
|
@ -25,7 +25,7 @@ def fillfuncargs(function):
|
||||||
except request.Error:
|
except request.Error:
|
||||||
request._raiselookupfailed()
|
request._raiselookupfailed()
|
||||||
|
|
||||||
class RunSpecs:
|
class FuncSpecs:
|
||||||
def __init__(self, function, config=None, cls=None, module=None):
|
def __init__(self, function, config=None, cls=None, module=None):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.module = module
|
self.module = module
|
||||||
|
@ -33,22 +33,14 @@ class RunSpecs:
|
||||||
self.funcargnames = getfuncargnames(function)
|
self.funcargnames = getfuncargnames(function)
|
||||||
self.cls = cls
|
self.cls = cls
|
||||||
self.module = module
|
self.module = module
|
||||||
self._combinations = []
|
self._calls = []
|
||||||
|
|
||||||
def addfuncarg(self, argname, value):
|
def addcall(self, **kwargs):
|
||||||
if argname not in self.funcargnames:
|
for argname in kwargs:
|
||||||
raise ValueError("function %r has no funcarg %r" %(
|
if argname not in self.funcargnames:
|
||||||
self.function, argname))
|
raise ValueError("function %r has no funcarg %r" %(
|
||||||
newcombi = []
|
self.function, argname))
|
||||||
if not self._combinations:
|
self._calls.append(kwargs)
|
||||||
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)
|
|
||||||
|
|
||||||
class FunctionCollector(py.test.collect.Collector):
|
class FunctionCollector(py.test.collect.Collector):
|
||||||
def __init__(self, name, parent, combinations):
|
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. """
|
""" return custom item/collector for a python object in a module, or None. """
|
||||||
pytest_pycollect_obj.firstresult = True
|
pytest_pycollect_obj.firstresult = True
|
||||||
|
|
||||||
def pytest_genfuncruns(self, runspec):
|
def pytest_genfunc(self, funcspec):
|
||||||
""" generate (multiple) parametrized calls to a test function."""
|
""" generate (multiple) parametrized calls to a test function."""
|
||||||
|
|
||||||
def pytest_collectstart(self, collector):
|
def pytest_collectstart(self, collector):
|
||||||
|
|
|
@ -172,13 +172,13 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
|
||||||
# to work to get at the class
|
# to work to get at the class
|
||||||
clscol = self.getclasscollector()
|
clscol = self.getclasscollector()
|
||||||
cls = clscol and clscol.obj or None
|
cls = clscol and clscol.obj or None
|
||||||
runspec = funcargs.RunSpecs(funcobj, config=self.config, cls=cls, module=module)
|
funcspec = funcargs.FuncSpecs(funcobj, config=self.config, cls=cls, module=module)
|
||||||
gentesthook = self.config.hook.pytest_genfuncruns.clone(extralookup=module)
|
gentesthook = self.config.hook.pytest_genfunc.clone(extralookup=module)
|
||||||
gentesthook(runspec=runspec)
|
gentesthook(funcspec=funcspec)
|
||||||
if not runspec._combinations:
|
if not funcspec._calls:
|
||||||
return self.Function(name, parent=self)
|
return self.Function(name, parent=self)
|
||||||
return funcargs.FunctionCollector(name=name,
|
return funcargs.FunctionCollector(name=name,
|
||||||
parent=self, combinations=runspec._combinations)
|
parent=self, combinations=funcspec._calls)
|
||||||
|
|
||||||
class Module(py.test.collect.File, PyCollectorMixin):
|
class Module(py.test.collect.File, PyCollectorMixin):
|
||||||
def _getobj(self):
|
def _getobj(self):
|
||||||
|
|
|
@ -131,71 +131,58 @@ class TestRequest:
|
||||||
req = funcargs.FuncargRequest(item, "xxx")
|
req = funcargs.FuncargRequest(item, "xxx")
|
||||||
assert req.fspath == modcol.fspath
|
assert req.fspath == modcol.fspath
|
||||||
|
|
||||||
class TestRunSpecs:
|
class TestFuncSpecs:
|
||||||
def test_no_funcargs(self, testdir):
|
def test_no_funcargs(self, testdir):
|
||||||
def function(): pass
|
def function(): pass
|
||||||
runspec = funcargs.RunSpecs(function)
|
funcspec = funcargs.FuncSpecs(function)
|
||||||
assert not runspec.funcargnames
|
assert not funcspec.funcargnames
|
||||||
|
|
||||||
def test_function_basic(self):
|
def test_function_basic(self):
|
||||||
def func(arg1, arg2="qwe"): pass
|
def func(arg1, arg2="qwe"): pass
|
||||||
runspec = funcargs.RunSpecs(func)
|
funcspec = funcargs.FuncSpecs(func)
|
||||||
assert len(runspec.funcargnames) == 1
|
assert len(funcspec.funcargnames) == 1
|
||||||
assert 'arg1' in runspec.funcargnames
|
assert 'arg1' in funcspec.funcargnames
|
||||||
assert runspec.function is func
|
assert funcspec.function is func
|
||||||
assert runspec.cls is None
|
assert funcspec.cls is None
|
||||||
|
|
||||||
def test_addfuncarg_basic(self):
|
def test_addcall_basic(self):
|
||||||
def func(arg1): pass
|
def func(arg1): pass
|
||||||
runspec = funcargs.RunSpecs(func)
|
funcspec = funcargs.FuncSpecs(func)
|
||||||
py.test.raises(ValueError, """
|
py.test.raises(ValueError, """
|
||||||
runspec.addfuncarg("notexists", 100)
|
funcspec.addcall(notexists=100)
|
||||||
""")
|
""")
|
||||||
runspec.addfuncarg("arg1", 100)
|
funcspec.addcall(arg1=100)
|
||||||
assert len(runspec._combinations) == 1
|
assert len(funcspec._calls) == 1
|
||||||
assert runspec._combinations[0] == {'arg1': 100}
|
assert funcspec._calls[0] == {'arg1': 100}
|
||||||
|
|
||||||
def test_addfuncarg_two(self):
|
def test_addcall_two(self):
|
||||||
def func(arg1): pass
|
def func(arg1): pass
|
||||||
runspec = funcargs.RunSpecs(func)
|
funcspec = funcargs.FuncSpecs(func)
|
||||||
runspec.addfuncarg("arg1", 100)
|
funcspec.addcall(arg1=100)
|
||||||
runspec.addfuncarg("arg1", 101)
|
funcspec.addcall(arg1=101)
|
||||||
assert len(runspec._combinations) == 2
|
assert len(funcspec._calls) == 2
|
||||||
assert runspec._combinations[0] == {'arg1': 100}
|
assert funcspec._calls[0] == {'arg1': 100}
|
||||||
assert runspec._combinations[1] == {'arg1': 101}
|
assert funcspec._calls[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}
|
|
||||||
|
|
||||||
class TestGenfuncFunctional:
|
class TestGenfuncFunctional:
|
||||||
def test_attributes(self, testdir):
|
def test_attributes(self, testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
import py
|
import py
|
||||||
def pytest_genfuncruns(runspec):
|
def pytest_genfunc(funcspec):
|
||||||
runspec.addfuncarg("runspec", runspec)
|
funcspec.addcall(funcspec=funcspec)
|
||||||
|
|
||||||
def test_function(runspec):
|
def test_function(funcspec):
|
||||||
assert runspec.config == py.test.config
|
assert funcspec.config == py.test.config
|
||||||
assert runspec.module.__name__ == __name__
|
assert funcspec.module.__name__ == __name__
|
||||||
assert runspec.function == test_function
|
assert funcspec.function == test_function
|
||||||
assert runspec.cls is None
|
assert funcspec.cls is None
|
||||||
class TestClass:
|
class TestClass:
|
||||||
def test_method(self, runspec):
|
def test_method(self, funcspec):
|
||||||
assert runspec.config == py.test.config
|
assert funcspec.config == py.test.config
|
||||||
assert runspec.module.__name__ == __name__
|
assert funcspec.module.__name__ == __name__
|
||||||
# XXX actually have the unbound test function here?
|
# XXX actually have the unbound test function here?
|
||||||
assert runspec.function == TestClass.test_method.im_func
|
assert funcspec.function == TestClass.test_method.im_func
|
||||||
assert runspec.cls == TestClass
|
assert funcspec.cls == TestClass
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest(p, "-v")
|
result = testdir.runpytest(p, "-v")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
|
@ -205,10 +192,10 @@ class TestGenfuncFunctional:
|
||||||
def test_arg_twice(self, testdir):
|
def test_arg_twice(self, testdir):
|
||||||
testdir.makeconftest("""
|
testdir.makeconftest("""
|
||||||
class ConftestPlugin:
|
class ConftestPlugin:
|
||||||
def pytest_genfuncruns(self, runspec):
|
def pytest_genfunc(self, funcspec):
|
||||||
assert "arg" in runspec.funcargnames
|
assert "arg" in funcspec.funcargnames
|
||||||
runspec.addfuncarg("arg", 10)
|
funcspec.addcall(arg=10)
|
||||||
runspec.addfuncarg("arg", 20)
|
funcspec.addcall(arg=20)
|
||||||
""")
|
""")
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
def test_myfunc(arg):
|
def test_myfunc(arg):
|
||||||
|
@ -223,9 +210,9 @@ class TestGenfuncFunctional:
|
||||||
|
|
||||||
def test_two_functions(self, testdir):
|
def test_two_functions(self, testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
def pytest_genfuncruns(runspec):
|
def pytest_genfunc(funcspec):
|
||||||
runspec.addfuncarg("arg1", 10)
|
funcspec.addcall(arg1=10)
|
||||||
runspec.addfuncarg("arg1", 20)
|
funcspec.addcall(arg1=20)
|
||||||
|
|
||||||
def test_func1(arg1):
|
def test_func1(arg1):
|
||||||
assert arg1 == 10
|
assert arg1 == 10
|
||||||
|
@ -243,20 +230,17 @@ class TestGenfuncFunctional:
|
||||||
def test_genfuncarg_inmodule(self, testdir):
|
def test_genfuncarg_inmodule(self, testdir):
|
||||||
testdir.makeconftest("""
|
testdir.makeconftest("""
|
||||||
class ConftestPlugin:
|
class ConftestPlugin:
|
||||||
def pytest_genfuncruns(self, runspec):
|
def pytest_genfunc(self, funcspec):
|
||||||
assert "arg" in runspec.funcargnames
|
assert "arg1" in funcspec.funcargnames
|
||||||
runspec.addfuncarg("arg", 10)
|
funcspec.addcall(arg1=1, arg2=2)
|
||||||
""")
|
""")
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
def pytest_genfuncruns(runspec):
|
def pytest_genfunc(funcspec):
|
||||||
runspec.addfuncarg("arg2", 10)
|
funcspec.addcall(arg1=10, arg2=10)
|
||||||
runspec.addfuncarg("arg2", 20)
|
|
||||||
runspec.addfuncarg("classarg", 17)
|
|
||||||
|
|
||||||
class TestClass:
|
class TestClass:
|
||||||
def test_myfunc(self, arg, arg2, classarg):
|
def test_myfunc(self, arg1, arg2):
|
||||||
assert classarg == 17
|
assert arg1 == arg2
|
||||||
assert arg == arg2
|
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest("-v", p)
|
result = testdir.runpytest("-v", p)
|
||||||
assert result.stdout.fnmatch_lines([
|
assert result.stdout.fnmatch_lines([
|
||||||
|
|
Loading…
Reference in New Issue