introduce @pytest.mark.setup decorated function,
extend newexamples.txt and draft a V4 resources API doc.
This commit is contained in:
parent
d4a487c725
commit
fa61927c6b
|
@ -1,2 +1,2 @@
|
||||||
#
|
#
|
||||||
__version__ = '2.3.0.dev3'
|
__version__ = '2.3.0.dev4'
|
||||||
|
|
|
@ -8,6 +8,8 @@ import os, sys, imp
|
||||||
from _pytest.monkeypatch import monkeypatch
|
from _pytest.monkeypatch import monkeypatch
|
||||||
from py._code.code import TerminalRepr
|
from py._code.code import TerminalRepr
|
||||||
|
|
||||||
|
from _pytest.mark import MarkInfo
|
||||||
|
|
||||||
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
|
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
|
||||||
|
|
||||||
# exitcodes for the command line
|
# exitcodes for the command line
|
||||||
|
@ -422,6 +424,7 @@ class FuncargManager:
|
||||||
self.arg2facspec = {}
|
self.arg2facspec = {}
|
||||||
session.config.pluginmanager.register(self, "funcmanage")
|
session.config.pluginmanager.register(self, "funcmanage")
|
||||||
self._holderobjseen = set()
|
self._holderobjseen = set()
|
||||||
|
self.setuplist = []
|
||||||
|
|
||||||
### XXX this hook should be called for historic events like pytest_configure
|
### XXX this hook should be called for historic events like pytest_configure
|
||||||
### so that we don't have to do the below pytest_collection hook
|
### so that we don't have to do the below pytest_collection hook
|
||||||
|
@ -445,6 +448,9 @@ class FuncargManager:
|
||||||
|
|
||||||
def pytest_generate_tests(self, metafunc):
|
def pytest_generate_tests(self, metafunc):
|
||||||
funcargnames = list(metafunc.funcargnames)
|
funcargnames = list(metafunc.funcargnames)
|
||||||
|
setuplist, allargnames = self.getsetuplist(metafunc.parentid)
|
||||||
|
#print "setuplist, allargnames", setuplist, allargnames
|
||||||
|
funcargnames.extend(allargnames)
|
||||||
seen = set()
|
seen = set()
|
||||||
while funcargnames:
|
while funcargnames:
|
||||||
argname = funcargnames.pop(0)
|
argname = funcargnames.pop(0)
|
||||||
|
@ -465,6 +471,8 @@ class FuncargManager:
|
||||||
newfuncargnames.remove("request")
|
newfuncargnames.remove("request")
|
||||||
funcargnames.extend(newfuncargnames)
|
funcargnames.extend(newfuncargnames)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _parsefactories(self, holderobj, nodeid):
|
def _parsefactories(self, holderobj, nodeid):
|
||||||
if holderobj in self._holderobjseen:
|
if holderobj in self._holderobjseen:
|
||||||
return
|
return
|
||||||
|
@ -473,20 +481,36 @@ class FuncargManager:
|
||||||
for name in dir(holderobj):
|
for name in dir(holderobj):
|
||||||
#print "check", holderobj, name
|
#print "check", holderobj, name
|
||||||
obj = getattr(holderobj, name)
|
obj = getattr(holderobj, name)
|
||||||
|
if not callable(obj):
|
||||||
|
continue
|
||||||
# funcarg factories either have a pytest_funcarg__ prefix
|
# funcarg factories either have a pytest_funcarg__ prefix
|
||||||
# or are "funcarg" marked
|
# or are "funcarg" marked
|
||||||
if hasattr(obj, "funcarg"):
|
if hasattr(obj, "funcarg"):
|
||||||
if name.startswith(self._argprefix):
|
assert not name.startswith(self._argprefix)
|
||||||
argname = name[len(self._argprefix):]
|
|
||||||
else:
|
|
||||||
argname = name
|
argname = name
|
||||||
elif name.startswith(self._argprefix):
|
elif name.startswith(self._argprefix):
|
||||||
argname = name[len(self._argprefix):]
|
argname = name[len(self._argprefix):]
|
||||||
else:
|
else:
|
||||||
|
# no funcargs. check if we have a setup function.
|
||||||
|
setup = getattr(obj, "setup", None)
|
||||||
|
if setup is not None and isinstance(setup, MarkInfo):
|
||||||
|
self.setuplist.append((nodeid, obj))
|
||||||
continue
|
continue
|
||||||
faclist = self.arg2facspec.setdefault(argname, [])
|
faclist = self.arg2facspec.setdefault(argname, [])
|
||||||
faclist.append((nodeid, obj))
|
faclist.append((nodeid, obj))
|
||||||
|
|
||||||
|
def getsetuplist(self, nodeid):
|
||||||
|
l = []
|
||||||
|
allargnames = set()
|
||||||
|
for baseid, setup in self.setuplist:
|
||||||
|
#print "check", baseid, setup
|
||||||
|
if nodeid.startswith(baseid):
|
||||||
|
funcargnames = getfuncargnames(setup)
|
||||||
|
l.append((setup, funcargnames))
|
||||||
|
allargnames.update(funcargnames)
|
||||||
|
return l, allargnames
|
||||||
|
|
||||||
|
|
||||||
def getfactorylist(self, argname, nodeid, function, raising=True):
|
def getfactorylist(self, argname, nodeid, function, raising=True):
|
||||||
try:
|
try:
|
||||||
factorydef = self.arg2facspec[argname]
|
factorydef = self.arg2facspec[argname]
|
||||||
|
|
|
@ -834,6 +834,8 @@ class Function(FunctionMixin, pytest.Item):
|
||||||
def setup(self):
|
def setup(self):
|
||||||
super(Function, self).setup()
|
super(Function, self).setup()
|
||||||
fillfuncargs(self)
|
fillfuncargs(self)
|
||||||
|
if hasattr(self, "_request"):
|
||||||
|
self._request._callsetup()
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
try:
|
try:
|
||||||
|
@ -990,6 +992,22 @@ class FuncargRequest:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def _callsetup(self):
|
||||||
|
setuplist, allnames = self.funcargmanager.getsetuplist(
|
||||||
|
self._pyfuncitem.nodeid)
|
||||||
|
for setupfunc, funcargnames in setuplist:
|
||||||
|
kwargs = {}
|
||||||
|
for name in funcargnames:
|
||||||
|
if name == "request":
|
||||||
|
kwargs[name] = self
|
||||||
|
else:
|
||||||
|
kwargs[name] = self.getfuncargvalue(name)
|
||||||
|
scope = readscope(setupfunc, "setup")
|
||||||
|
if scope is None:
|
||||||
|
setupfunc(**kwargs)
|
||||||
|
else:
|
||||||
|
self.cached_setup(lambda: setupfunc(**kwargs), scope=scope)
|
||||||
|
|
||||||
def getfuncargvalue(self, argname):
|
def getfuncargvalue(self, argname):
|
||||||
""" Retrieve a function argument by name for this test
|
""" Retrieve a function argument by name for this test
|
||||||
function invocation. This allows one function argument factory
|
function invocation. This allows one function argument factory
|
||||||
|
@ -1030,10 +1048,8 @@ class FuncargRequest:
|
||||||
mp.setattr(self, 'param', param, raising=False)
|
mp.setattr(self, 'param', param, raising=False)
|
||||||
|
|
||||||
# implemenet funcarg marker scope
|
# implemenet funcarg marker scope
|
||||||
marker = getattr(funcargfactory, "funcarg", None)
|
scope = readscope(funcargfactory, "funcarg")
|
||||||
scope = None
|
|
||||||
if marker is not None:
|
|
||||||
scope = marker.kwargs.get("scope")
|
|
||||||
if scope is not None:
|
if scope is not None:
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
if scopemismatch(self.scope, scope):
|
if scopemismatch(self.scope, scope):
|
||||||
|
@ -1106,3 +1122,7 @@ def slice_kwargs(names, kwargs):
|
||||||
new_kwargs[name] = kwargs[name]
|
new_kwargs[name] = kwargs[name]
|
||||||
return new_kwargs
|
return new_kwargs
|
||||||
|
|
||||||
|
def readscope(func, markattr):
|
||||||
|
marker = getattr(func, markattr, None)
|
||||||
|
if marker is not None:
|
||||||
|
return marker.kwargs.get("scope")
|
||||||
|
|
|
@ -48,7 +48,7 @@ If you run the tests::
|
||||||
================================= FAILURES =================================
|
================================= FAILURES =================================
|
||||||
________________________________ test_ehlo _________________________________
|
________________________________ test_ehlo _________________________________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x28599e0>
|
smtp = <smtplib.SMTP instance at 0x20ba7e8>
|
||||||
|
|
||||||
def test_ehlo(smtp):
|
def test_ehlo(smtp):
|
||||||
response = smtp.ehlo()
|
response = smtp.ehlo()
|
||||||
|
@ -60,7 +60,7 @@ If you run the tests::
|
||||||
test_module.py:5: AssertionError
|
test_module.py:5: AssertionError
|
||||||
________________________________ test_noop _________________________________
|
________________________________ test_noop _________________________________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x28599e0>
|
smtp = <smtplib.SMTP instance at 0x20ba7e8>
|
||||||
|
|
||||||
def test_noop(smtp):
|
def test_noop(smtp):
|
||||||
response = smtp.noop()
|
response = smtp.noop()
|
||||||
|
@ -69,7 +69,7 @@ If you run the tests::
|
||||||
E assert 0
|
E assert 0
|
||||||
|
|
||||||
test_module.py:10: AssertionError
|
test_module.py:10: AssertionError
|
||||||
2 failed in 0.14 seconds
|
2 failed in 0.27 seconds
|
||||||
|
|
||||||
you will see the two ``assert 0`` failing and can see that
|
you will see the two ``assert 0`` failing and can see that
|
||||||
the same (session-scoped) object was passed into the two test functions.
|
the same (session-scoped) object was passed into the two test functions.
|
||||||
|
@ -100,7 +100,7 @@ another run::
|
||||||
================================= FAILURES =================================
|
================================= FAILURES =================================
|
||||||
__________________________ test_ehlo[merlinux.eu] __________________________
|
__________________________ test_ehlo[merlinux.eu] __________________________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x2bf3d40>
|
smtp = <smtplib.SMTP instance at 0x2a51830>
|
||||||
|
|
||||||
def test_ehlo(smtp):
|
def test_ehlo(smtp):
|
||||||
response = smtp.ehlo()
|
response = smtp.ehlo()
|
||||||
|
@ -112,7 +112,7 @@ another run::
|
||||||
test_module.py:5: AssertionError
|
test_module.py:5: AssertionError
|
||||||
________________________ test_ehlo[mail.python.org] ________________________
|
________________________ test_ehlo[mail.python.org] ________________________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x2bf9170>
|
smtp = <smtplib.SMTP instance at 0x2a56c20>
|
||||||
|
|
||||||
def test_ehlo(smtp):
|
def test_ehlo(smtp):
|
||||||
response = smtp.ehlo()
|
response = smtp.ehlo()
|
||||||
|
@ -123,7 +123,7 @@ another run::
|
||||||
test_module.py:4: AssertionError
|
test_module.py:4: AssertionError
|
||||||
__________________________ test_noop[merlinux.eu] __________________________
|
__________________________ test_noop[merlinux.eu] __________________________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x2bf3d40>
|
smtp = <smtplib.SMTP instance at 0x2a51830>
|
||||||
|
|
||||||
def test_noop(smtp):
|
def test_noop(smtp):
|
||||||
response = smtp.noop()
|
response = smtp.noop()
|
||||||
|
@ -134,7 +134,7 @@ another run::
|
||||||
test_module.py:10: AssertionError
|
test_module.py:10: AssertionError
|
||||||
________________________ test_noop[mail.python.org] ________________________
|
________________________ test_noop[mail.python.org] ________________________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x2bf9170>
|
smtp = <smtplib.SMTP instance at 0x2a56c20>
|
||||||
|
|
||||||
def test_noop(smtp):
|
def test_noop(smtp):
|
||||||
response = smtp.noop()
|
response = smtp.noop()
|
||||||
|
@ -143,9 +143,9 @@ another run::
|
||||||
E assert 0
|
E assert 0
|
||||||
|
|
||||||
test_module.py:10: AssertionError
|
test_module.py:10: AssertionError
|
||||||
4 failed in 5.70 seconds
|
4 failed in 6.91 seconds
|
||||||
closing <smtplib.SMTP instance at 0x2bf9170>
|
closing <smtplib.SMTP instance at 0x2a56c20>
|
||||||
closing <smtplib.SMTP instance at 0x2bf3d40>
|
closing <smtplib.SMTP instance at 0x2a51830>
|
||||||
|
|
||||||
We get four failures because we are running the two tests twice with
|
We get four failures because we are running the two tests twice with
|
||||||
different ``smtp`` instantiations as defined on the factory.
|
different ``smtp`` instantiations as defined on the factory.
|
||||||
|
@ -157,7 +157,7 @@ You can look at what tests pytest collects without running them::
|
||||||
|
|
||||||
$ py.test --collectonly
|
$ py.test --collectonly
|
||||||
=========================== test session starts ============================
|
=========================== test session starts ============================
|
||||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev3
|
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev4
|
||||||
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
|
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
|
||||||
collecting ... collected 4 items
|
collecting ... collected 4 items
|
||||||
<Module 'test_module.py'>
|
<Module 'test_module.py'>
|
||||||
|
@ -174,10 +174,113 @@ And you can run without output capturing and minimized failure reporting to chec
|
||||||
collecting ... collected 4 items
|
collecting ... collected 4 items
|
||||||
FFFF
|
FFFF
|
||||||
================================= FAILURES =================================
|
================================= FAILURES =================================
|
||||||
/home/hpk/tmp/doc-exec-330/test_module.py:5: assert 0
|
/home/hpk/tmp/doc-exec-361/test_module.py:5: assert 0
|
||||||
/home/hpk/tmp/doc-exec-330/test_module.py:4: assert 'merlinux' in 'mail.python.org\nSIZE 10240000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN'
|
/home/hpk/tmp/doc-exec-361/test_module.py:4: assert 'merlinux' in 'mail.python.org\nSIZE 10240000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN'
|
||||||
/home/hpk/tmp/doc-exec-330/test_module.py:10: assert 0
|
/home/hpk/tmp/doc-exec-361/test_module.py:10: assert 0
|
||||||
/home/hpk/tmp/doc-exec-330/test_module.py:10: assert 0
|
/home/hpk/tmp/doc-exec-361/test_module.py:10: assert 0
|
||||||
4 failed in 6.02 seconds
|
4 failed in 6.83 seconds
|
||||||
closing <smtplib.SMTP instance at 0x1f5ef38>
|
closing <smtplib.SMTP instance at 0x236da28>
|
||||||
closing <smtplib.SMTP instance at 0x1f5acf8>
|
closing <smtplib.SMTP instance at 0x23687e8>
|
||||||
|
|
||||||
|
.. _`new_setup`:
|
||||||
|
|
||||||
|
``@pytest.mark.setup``: xUnit on steroids
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
.. regendoc:wipe
|
||||||
|
|
||||||
|
.. versionadded:: 2.3
|
||||||
|
|
||||||
|
The ``@pytest.mark.setup`` marker allows
|
||||||
|
|
||||||
|
* to mark a function as a setup/fixture method; the function can itself
|
||||||
|
receive funcargs
|
||||||
|
* to set a scope which determines the level of caching and how often
|
||||||
|
the setup function is going to be called.
|
||||||
|
|
||||||
|
Here is a simple example which configures a global funcarg without
|
||||||
|
the test needing to have it in its signature::
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.funcarg(scope="module")
|
||||||
|
def resource(request, tmpdir):
|
||||||
|
def fin():
|
||||||
|
print "finalize", tmpdir
|
||||||
|
request.addfinalizer(fin)
|
||||||
|
print "created resource", tmpdir
|
||||||
|
return tmpdir
|
||||||
|
|
||||||
|
And the test file contains a setup function using this resource::
|
||||||
|
|
||||||
|
# content of test_module.py
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.setup(scope="function")
|
||||||
|
def setresource(resource):
|
||||||
|
global myresource
|
||||||
|
myresource = resource
|
||||||
|
|
||||||
|
def test_1():
|
||||||
|
assert myresource
|
||||||
|
print "using myresource", myresource
|
||||||
|
|
||||||
|
def test_2():
|
||||||
|
assert myresource
|
||||||
|
print "using myresource", myresource
|
||||||
|
|
||||||
|
Let's run this module::
|
||||||
|
|
||||||
|
$ py.test -qs
|
||||||
|
collecting ... collected 2 items
|
||||||
|
..
|
||||||
|
2 passed in 0.24 seconds
|
||||||
|
created resource /home/hpk/tmp/pytest-3715/test_10
|
||||||
|
using myresource /home/hpk/tmp/pytest-3715/test_10
|
||||||
|
using myresource /home/hpk/tmp/pytest-3715/test_10
|
||||||
|
finalize /home/hpk/tmp/pytest-3715/test_10
|
||||||
|
|
||||||
|
The two test functions will see the same resource instance because it has
|
||||||
|
a module life cycle or scope.
|
||||||
|
|
||||||
|
The resource funcarg can later add parametrization without any test
|
||||||
|
or setup code needing to change::
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.funcarg(scope="module", params=["aaa", "bbb"])
|
||||||
|
def resource(request, tmpdir):
|
||||||
|
newtmp = tmpdir.join(request.param)
|
||||||
|
def fin():
|
||||||
|
print "finalize", newtmp
|
||||||
|
request.addfinalizer(fin)
|
||||||
|
print "created resource", newtmp
|
||||||
|
return newtmp
|
||||||
|
|
||||||
|
Running this will run four tests::
|
||||||
|
|
||||||
|
$ py.test -qs
|
||||||
|
collecting ... collected 4 items
|
||||||
|
....
|
||||||
|
4 passed in 0.24 seconds
|
||||||
|
created resource /home/hpk/tmp/pytest-3716/test_1_aaa_0/aaa
|
||||||
|
using myresource /home/hpk/tmp/pytest-3716/test_1_aaa_0/aaa
|
||||||
|
created resource /home/hpk/tmp/pytest-3716/test_1_bbb_0/bbb
|
||||||
|
using myresource /home/hpk/tmp/pytest-3716/test_1_bbb_0/bbb
|
||||||
|
using myresource /home/hpk/tmp/pytest-3716/test_1_aaa_0/aaa
|
||||||
|
using myresource /home/hpk/tmp/pytest-3716/test_1_bbb_0/bbb
|
||||||
|
finalize /home/hpk/tmp/pytest-3716/test_1_bbb_0/bbb
|
||||||
|
finalize /home/hpk/tmp/pytest-3716/test_1_aaa_0/aaa
|
||||||
|
|
||||||
|
Each parameter causes the creation of a respective resource and the
|
||||||
|
unchanged test module uses it in its ``@setup`` decorated method.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Currently, parametrized tests are sorted by test function location
|
||||||
|
so a test function will execute multiple times with different parametrized
|
||||||
|
funcargs. If you have class/module/session scoped funcargs and
|
||||||
|
they cause global side effects this can cause problems because the
|
||||||
|
code under test may not be prepared to deal with it.
|
||||||
|
|
|
@ -1,43 +1,55 @@
|
||||||
|
|
||||||
V3: Creating and working with parametrized test resources
|
V4: Creating and working with parametrized resources
|
||||||
===============================================================
|
===============================================================
|
||||||
|
|
||||||
**Target audience**: Reading this document requires basic knowledge of
|
**Target audience**: Reading this document requires basic knowledge of
|
||||||
python testing, xUnit setup methods and the basic pytest funcarg mechanism,
|
python testing, xUnit setup methods and the basic pytest funcarg mechanism,
|
||||||
see http://pytest.org/latest/funcargs.html
|
see http://pytest.org/latest/funcargs.html
|
||||||
|
|
||||||
**Abstract**: pytest-2.X provides more powerful and more flexible funcarg
|
**Abstract**: pytest-2.X provides yet more powerful and flexible
|
||||||
and setup machinery. It does so by introducing a new @funcarg and a
|
fixture machinery by introducing:
|
||||||
new @setup marker which allows to define scoping and parametrization
|
|
||||||
parameters. If using ``@funcarg``, following the ``pytest_funcarg__``
|
* a new ``@pytest.mark.funcarg`` marker to define funcarg factories and their
|
||||||
naming pattern becomes optional. Functions decorated with ``@setup``
|
scoping and parametrization. No special ``pytest_funcarg__`` naming there.
|
||||||
are called independenlty from the definition of funcargs but can
|
|
||||||
access funcarg values if needed. This allows for ultimate flexibility
|
* a new ``@pytest.mark.setup`` marker to define setup functions and their
|
||||||
in designing your test fixtures and their parametrization. Also,
|
scoping.
|
||||||
you can now use ``py.test --collectonly`` to inspect your fixture
|
|
||||||
setup. Nonwithstanding these extensions, pre-existing test suites
|
* directly use funcargs through funcarg factory signatures
|
||||||
and plugins written to work for previous pytest versions shall run unmodified.
|
|
||||||
|
Both funcarg factories and setup functions can be defined in test modules,
|
||||||
|
classes, conftest.py files and installed plugins.
|
||||||
|
|
||||||
|
The introduction of these two markers lifts several prior limitations
|
||||||
|
and allows to easily define and implement complex testing scenarios.
|
||||||
|
|
||||||
|
Nonwithstanding these extensions, already existing test suites and plugins
|
||||||
|
written to work for previous pytest versions shall run unmodified.
|
||||||
|
|
||||||
|
|
||||||
**Changes**: This V3 draft is based on incorporating and thinking about
|
**Changes**: This V4 draft is based on incorporating and thinking about
|
||||||
feedback provided by Floris Bruynooghe, Carl Meyer and Samuele Pedroni.
|
feedback on previous versions provided by Floris Bruynooghe, Carl Meyer,
|
||||||
It remains as draft documentation, pending further refinements and
|
Ronny Pfannschmidt and Samuele Pedroni. It remains as draft
|
||||||
changes according to implementation or backward compatibility issues.
|
documentation, pending further refinements and changes according to
|
||||||
The main changes to V2 are:
|
implementation or backward compatibility issues. The main changes are:
|
||||||
|
|
||||||
* Collapse funcarg factory decorator into a single "@funcarg" one.
|
* Collapse funcarg factory decorators into a single "@funcarg" one.
|
||||||
You can specify scopes and params with it. Moreover, if you supply
|
You can specify scopes and params with it. When using the decorator
|
||||||
a "name" you do not need to follow the "pytest_funcarg__NAME" naming
|
the "pytest_funcarg__" prefix becomes optional.
|
||||||
pattern. Keeping with "funcarg" naming arguable now makes more
|
|
||||||
sense since the main interface using these resources are test and
|
|
||||||
setup functions. Keeping it probably causes the least semantic friction.
|
|
||||||
|
|
||||||
* Drop setup_directory/setup_session and introduce a new @setup
|
* funcarg factories can now use funcargs themselves
|
||||||
decorator similar to the @funcarg one but accepting funcargs.
|
|
||||||
|
|
||||||
* cosnider the extended setup_X funcargs for dropping because
|
* Drop setup/directory scope from this draft
|
||||||
the new @setup decorator probably is more flexible and introduces
|
|
||||||
less implementation complexity.
|
* introduce a new @setup decorator similar to the @funcarg one
|
||||||
|
except that setup-markers cannot define parametriation themselves.
|
||||||
|
Instead they can easily depend on a parametrized funcarg (which
|
||||||
|
must not be visible at test function signatures).
|
||||||
|
|
||||||
|
* drop consideration of setup_X support for funcargs because
|
||||||
|
it is less flexible and probably causes more implementation
|
||||||
|
troubles than the current @setup approach which can share
|
||||||
|
a lot of logic with the @funcarg one.
|
||||||
|
|
||||||
.. currentmodule:: _pytest
|
.. currentmodule:: _pytest
|
||||||
|
|
||||||
|
@ -78,17 +90,13 @@ There are some problems with this approach:
|
||||||
``extrakey`` parameter containing ``request.param`` to the
|
``extrakey`` parameter containing ``request.param`` to the
|
||||||
:py:func:`~python.Request.cached_setup` call.
|
:py:func:`~python.Request.cached_setup` call.
|
||||||
|
|
||||||
3. the current implementation is inefficient: it performs factory discovery
|
3. there is no way how you can make use of funcarg factories
|
||||||
each time a "db" argument is required. This discovery wrongly happens at
|
in xUnit setup methods.
|
||||||
setup-time.
|
|
||||||
|
|
||||||
4. there is no way how you can use funcarg factories, let alone
|
4. A non-parametrized funcarg factory cannot use a parametrized
|
||||||
parametrization, when your tests use the xUnit setup_X approach.
|
funcarg resource if it isn't stated in the test function signature.
|
||||||
|
|
||||||
5. there is no way to specify a per-directory scope for caching.
|
The following sections address the advances which solve all of these problems.
|
||||||
|
|
||||||
In the following sections, API extensions are presented to solve
|
|
||||||
each of these problems.
|
|
||||||
|
|
||||||
|
|
||||||
Direct scoping of funcarg factories
|
Direct scoping of funcarg factories
|
||||||
|
@ -158,7 +166,7 @@ factory function.
|
||||||
Direct usage of funcargs with funcargs factories
|
Direct usage of funcargs with funcargs factories
|
||||||
----------------------------------------------------------
|
----------------------------------------------------------
|
||||||
|
|
||||||
.. note:: Not Implemented - unclear if to.
|
.. note:: Implemented.
|
||||||
|
|
||||||
You can now directly use funcargs in funcarg factories. Example::
|
You can now directly use funcargs in funcarg factories. Example::
|
||||||
|
|
||||||
|
@ -168,33 +176,39 @@ You can now directly use funcargs in funcarg factories. Example::
|
||||||
|
|
||||||
Apart from convenience it also solves an issue when your factory
|
Apart from convenience it also solves an issue when your factory
|
||||||
depends on a parametrized funcarg. Previously, a call to
|
depends on a parametrized funcarg. Previously, a call to
|
||||||
``request.getfuncargvalue()`` would not allow pytest to know
|
``request.getfuncargvalue()`` happens at test execution time and
|
||||||
at collection time about the fact that a required resource is
|
thus pytest would not know at collection time about the fact that
|
||||||
actually parametrized.
|
a required resource is parametrized.
|
||||||
|
|
||||||
|
No ``pytest_funcarg__`` prefix when using @funcarg decorator
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
|
||||||
The "pytest_funcarg__" prefix becomes optional
|
|
||||||
-----------------------------------------------------
|
|
||||||
|
|
||||||
.. note:: Implemented
|
.. note:: Implemented
|
||||||
|
|
||||||
When using the ``@funcarg`` decorator you do not need to use
|
When using the ``@funcarg`` decorator the name of the function
|
||||||
the ``pytest_funcarg__`` prefix any more::
|
does not need to (and in fact cannot) use the ``pytest_funcarg__``
|
||||||
|
naming::
|
||||||
|
|
||||||
@pytest.mark.funcarg
|
@pytest.mark.funcarg
|
||||||
def db(request):
|
def db(request):
|
||||||
...
|
...
|
||||||
|
|
||||||
The name under which the funcarg resource can be requested is ``db``.
|
The name under which the funcarg resource can be requested is ``db``.
|
||||||
Any ``pytest_funcarg__`` prefix will be stripped. Note that a an
|
|
||||||
unqualified funcarg-marker implies a scope of "function" meaning
|
You can also use the "old" non-decorator way of specifying funcarg factories
|
||||||
that the funcarg factory will be called for each test function invocation.
|
aka::
|
||||||
|
|
||||||
|
def pytest_funcarg__db(request):
|
||||||
|
...
|
||||||
|
|
||||||
|
It is recommended to use the funcarg-decorator, however.
|
||||||
|
|
||||||
|
|
||||||
|
solving per-session setup / the new @setup marker
|
||||||
|
--------------------------------------------------------------
|
||||||
|
|
||||||
support for a new @setup marker
|
.. note:: Implemented, at least working for basic situations.
|
||||||
------------------------------------------------------
|
|
||||||
|
|
||||||
.. note:: Not-Implemented, still under consideration if to.
|
|
||||||
|
|
||||||
pytest for a long time offered a pytest_configure and a pytest_sessionstart
|
pytest for a long time offered a pytest_configure and a pytest_sessionstart
|
||||||
hook which are often used to setup global resources. This suffers from
|
hook which are often used to setup global resources. This suffers from
|
||||||
|
@ -212,9 +226,7 @@ several problems:
|
||||||
fact that this hook is actually used for reporting, in particular
|
fact that this hook is actually used for reporting, in particular
|
||||||
the test-header with platform/custom information.
|
the test-header with platform/custom information.
|
||||||
|
|
||||||
4. there is no direct way how you can restrict setup to a directory scope.
|
Moreover, it is today not easy to define a scoped setup from plugins or
|
||||||
|
|
||||||
Moreover, it is today not easy to define scoped setup from plugins or
|
|
||||||
conftest files other than to implement a ``pytest_runtest_setup()`` hook
|
conftest files other than to implement a ``pytest_runtest_setup()`` hook
|
||||||
and caring for scoping/caching yourself. And it's virtually impossible
|
and caring for scoping/caching yourself. And it's virtually impossible
|
||||||
to do this with parametrization as ``pytest_runtest_setup()`` is called
|
to do this with parametrization as ``pytest_runtest_setup()`` is called
|
||||||
|
@ -222,222 +234,76 @@ during test execution and parametrization happens at collection time.
|
||||||
|
|
||||||
It follows that pytest_configure/session/runtest_setup are often not
|
It follows that pytest_configure/session/runtest_setup are often not
|
||||||
appropriate for implementing common fixture needs. Therefore,
|
appropriate for implementing common fixture needs. Therefore,
|
||||||
pytest-2.X introduces a new "@pytest.mark.setup" marker, accepting
|
pytest-2.X introduces a new "@pytest.mark.setup" marker which takes
|
||||||
the same parameters as the @funcargs decorator. The difference is
|
an optional "scope" parameter.
|
||||||
that the decorated function can accept function arguments itself
|
|
||||||
Example::
|
|
||||||
|
|
||||||
# content of conftest.py
|
See :ref:`new_setup` for examples.
|
||||||
import pytest
|
|
||||||
@pytest.mark.setup(scope="session")
|
|
||||||
def mysetup(db):
|
|
||||||
...
|
|
||||||
|
|
||||||
This ``mysetup`` function is going to be executed when the first
|
|
||||||
test in the directory tree executes. It is going to be executed once
|
|
||||||
per-session and it receives the ``db`` funcarg which must be of same
|
|
||||||
of higher scope; you e. g. generally cannot use a per-module or per-function
|
|
||||||
scoped resource in a session-scoped setup function.
|
|
||||||
|
|
||||||
You can also use ``@setup`` inside a test module or class::
|
|
||||||
|
|
||||||
# content of test_module.py
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
@pytest.mark.setup(scope="module", params=[1,2,3])
|
|
||||||
def modes(tmpdir, request):
|
|
||||||
# ...
|
|
||||||
|
|
||||||
This would execute the ``modes`` function once for each parameter
|
|
||||||
which will be put at ``request.param``. This request object offers
|
|
||||||
the ``addfinalizer(func)`` helper which allows to register a function
|
|
||||||
which will be executed when test functions within the specified scope
|
|
||||||
finished execution.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
For each scope, the funcargs will be setup and then the setup functions
|
|
||||||
will be called. This allows @setup-decorated functions to depend
|
|
||||||
on already setup funcarg values by accessing ``request.funcargs``.
|
|
||||||
|
|
||||||
Using funcarg resources in xUnit setup methods
|
|
||||||
------------------------------------------------------------
|
|
||||||
|
|
||||||
.. note:: Not implemented. Not clear if to.
|
|
||||||
|
|
||||||
XXX Consider this feature in contrast to the @setup feature - probably
|
|
||||||
introducing one of them is better and the @setup decorator is more flexible.
|
|
||||||
|
|
||||||
For a long time, pytest has recommended the usage of funcarg
|
|
||||||
factories as a primary means for managing resources in your test run.
|
|
||||||
It is a better approach than the jUnit-based approach in many cases, even
|
|
||||||
more with the new pytest-2.X features, because the funcarg resource factory
|
|
||||||
provides a single place to determine scoping and parametrization. Your tests
|
|
||||||
do not need to encode setup/teardown details in every test file's
|
|
||||||
setup_module/class/method.
|
|
||||||
|
|
||||||
However, the jUnit methods originally introduced by pytest to Python,
|
|
||||||
remain popoular with nose and unittest-based test suites. Without question,
|
|
||||||
there are large existing test suites using this paradigm. pytest-2.X
|
|
||||||
recognizes this fact and now offers direct integration with funcarg resources. Here is a basic example for getting a per-module tmpdir::
|
|
||||||
|
|
||||||
def setup_module(mod, tmpdir):
|
|
||||||
mod.tmpdir = tmpdir
|
|
||||||
|
|
||||||
This will trigger pytest's funcarg mechanism to create a value of
|
|
||||||
"tmpdir" which can then be used throughout the module as a global.
|
|
||||||
|
|
||||||
The new extension to setup_X methods also works in case a resource is
|
|
||||||
parametrized. For example, let's consider an setup_class example using
|
|
||||||
our "db" resource::
|
|
||||||
|
|
||||||
class TestClass:
|
|
||||||
def setup_class(cls, db):
|
|
||||||
cls.db = db
|
|
||||||
# perform some extra things on db
|
|
||||||
# so that test methods can work with it
|
|
||||||
|
|
||||||
With pytest-2.X the setup* methods will be discovered at collection-time,
|
|
||||||
allowing to seemlessly integrate this approach with parametrization,
|
|
||||||
allowing the factory specification to determine all details. The
|
|
||||||
setup_class itself does not itself need to be aware of the fact that
|
|
||||||
"db" might be a mysql/PG database.
|
|
||||||
Note that if the specified resource is provided only as a per-testfunction
|
|
||||||
resource, collection would early on report a ScopingMismatch error.
|
|
||||||
|
|
||||||
|
|
||||||
the "directory" caching scope
|
|
||||||
--------------------------------------------
|
|
||||||
|
|
||||||
.. note:: Not implemented.
|
|
||||||
|
|
||||||
All API accepting a scope (:py:func:`cached_setup()` and
|
|
||||||
the new funcarg/setup decorators) now also accept a "directory"
|
|
||||||
specification. This allows to restrict/cache resource values on a
|
|
||||||
per-directory level.
|
|
||||||
|
|
||||||
funcarg and setup discovery now happens at collection time
|
funcarg and setup discovery now happens at collection time
|
||||||
---------------------------------------------------------------------
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
.. note:: Partially implemented - collectonly shows no extra information
|
.. note::
|
||||||
|
Partially implemented - collectonly shows no extra information however.
|
||||||
|
|
||||||
pytest-2.X takes care to discover funcarg factories and setup_X methods
|
pytest-2.X takes care to discover funcarg factories and @setup methods
|
||||||
at collection time. This is more efficient especially for large test suites.
|
at collection time. This is more efficient especially for large test suites.
|
||||||
Moreover, a call to "py.test --collectonly" should be able to show
|
Moreover, a call to "py.test --collectonly" should be able to show
|
||||||
a lot of setup-information and thus presents a nice method to get an
|
a lot of setup-information and thus presents a nice method to get an
|
||||||
overview of resource management in your project.
|
overview of resource management in your project.
|
||||||
|
|
||||||
Implementation level
|
|
||||||
===================================================================
|
|
||||||
|
|
||||||
To implement the above new features, pytest-2.X grows some new hooks and
|
Sorting tests by funcarg scopes
|
||||||
methods. At the time of writing V2 and without actually implementing
|
-------------------------------------------
|
||||||
it, it is not clear how much of this new internal API will also be
|
|
||||||
exposed and advertised e. g. for plugin writers.
|
|
||||||
|
|
||||||
The main effort, however, will lie in revising what is done at
|
.. note:: Not implemented, Under consideration.
|
||||||
collection and what at test setup time. All funcarg factories and
|
|
||||||
xUnit setup methods need to be discovered at collection time
|
|
||||||
for the above mechanism to work. Additionally all test function
|
|
||||||
signatures need to be parsed in order to know which resources are
|
|
||||||
used. On the plus side, all previously collected fixtures and
|
|
||||||
test functions only need to be called, no discovery is neccessary
|
|
||||||
is required anymore.
|
|
||||||
|
|
||||||
the "request" object incorporates scope-specific behaviour
|
pytest by default sorts test items by their source location.
|
||||||
------------------------------------------------------------------
|
For class/module/session scoped funcargs it is not always
|
||||||
|
desirable to have multiple active funcargs. Sometimes,
|
||||||
|
the application under test may not even be able to handle it
|
||||||
|
because it relies on global state/side effects related to those
|
||||||
|
resources.
|
||||||
|
|
||||||
funcarg factories receive a request object to help with implementing
|
Therefore, pytest-2.3 tries to minimize the number of active
|
||||||
finalization and inspection of the requesting-context. If there is
|
resources and re-orders test items accordingly. Consider the following
|
||||||
no scoping is in effect, nothing much will change of the API behaviour.
|
example::
|
||||||
However, with scoping the request object represents the according context.
|
|
||||||
Let's consider this example::
|
|
||||||
|
|
||||||
@pytest.mark.factory_scope("class")
|
@pytest.mark.funcarg(scope="module", params=[1,2])
|
||||||
def pytest_funcarg__db(request):
|
def arg(request):
|
||||||
# ...
|
|
||||||
request.getfuncargvalue(...)
|
|
||||||
#
|
|
||||||
request.addfinalizer(db)
|
|
||||||
|
|
||||||
Due to the class-scope, the request object will:
|
|
||||||
|
|
||||||
- provide a ``None`` value for the ``request.function`` attribute.
|
|
||||||
- default to per-class finalization with the addfinalizer() call.
|
|
||||||
- raise a ScopeMismatchError if a more broadly scoped factory
|
|
||||||
wants to use a more tighly scoped factory (e.g. per-function)
|
|
||||||
|
|
||||||
In fact, the request object is likely going to provide a "node"
|
|
||||||
attribute, denoting the current collection node on which it internally
|
|
||||||
operates. (Prior to pytest-2.3 there already was an internal
|
|
||||||
_pyfuncitem).
|
|
||||||
|
|
||||||
As these are rather intuitive extensions, not much friction is expected
|
|
||||||
for test/plugin writers using the new scoping and parametrization mechanism.
|
|
||||||
It's, however, a serious internal effort to reorganize the pytest
|
|
||||||
implementation.
|
|
||||||
|
|
||||||
|
|
||||||
node.register_factory/getresource() methods
|
|
||||||
--------------------------------------------------------
|
|
||||||
|
|
||||||
In order to implement factory- and setup-method discovery at
|
|
||||||
collection time, a new node API will be introduced to allow
|
|
||||||
for factory registration and a getresource() call to obtain
|
|
||||||
created values. The exact details of this API remain subject
|
|
||||||
to experimentation. The basic idea is to introduce two new
|
|
||||||
methods to the Session class which is already available on all nodes
|
|
||||||
through the ``node.session`` attribute::
|
|
||||||
|
|
||||||
class Session:
|
|
||||||
def register_resource_factory(self, name, factory_or_list, scope):
|
|
||||||
""" register a resource factory for the given name.
|
|
||||||
|
|
||||||
:param name: Name of the resource.
|
|
||||||
:factory_or_list: a function or a list of functions creating
|
|
||||||
one or multiple resource values.
|
|
||||||
:param scope: a node instance. The factory will be only visisble
|
|
||||||
available for all descendant nodes.
|
|
||||||
specify the "session" instance for global availability
|
|
||||||
"""
|
|
||||||
|
|
||||||
def getresource(self, name, node):
|
|
||||||
""" get a named resource for the give node.
|
|
||||||
|
|
||||||
This method looks up a matching funcarg resource factory
|
|
||||||
and calls it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
.. todo::
|
|
||||||
|
|
||||||
XXX While this new API (or some variant of it) may suffices to implement
|
|
||||||
all of the described new usage-level features, it remains unclear how the
|
|
||||||
existing "@parametrize" or "metafunc.parametrize()" calls will map to it.
|
|
||||||
These parametrize-approaches tie resource parametrization to the
|
|
||||||
function/funcargs-usage rather than to the factories.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ISSUES
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
decorating a parametrized funcarg factory::
|
|
||||||
|
|
||||||
@pytest.mark.funcarg(scope="session", params=["mysql", "pg"])
|
|
||||||
def db(request):
|
|
||||||
...
|
...
|
||||||
class TestClass:
|
@pytest.mark.funcarg(scope="function", params=[1,2])
|
||||||
@pytest.mark.funcarg(scope="function")
|
def otherarg(request):
|
||||||
def something(self, request):
|
|
||||||
session_db = request.getfuncargvalue("db")
|
|
||||||
...
|
...
|
||||||
|
|
||||||
Here the function-scoped "something" factory uses the session-scoped
|
def test_0(otherarg):
|
||||||
"db" factory to perform some additional steps. The dependency, however,
|
pass
|
||||||
is only visible at setup-time, when the factory actually gets called.
|
def test_1(arg):
|
||||||
|
pass
|
||||||
|
def test_2(arg, otherarg):
|
||||||
|
pass
|
||||||
|
|
||||||
In order to allow parametrization at collection-time I see two ways:
|
if arg.1, arg.2, otherarg.1, otherarg.2 denote the respective
|
||||||
|
parametrized funcarg instances this will re-order test
|
||||||
|
execution like follows::
|
||||||
|
|
||||||
|
test_0(otherarg.1)
|
||||||
|
test_0(otherarg.2)
|
||||||
|
test_1(arg.1)
|
||||||
|
test_2(arg.1, otherarg.1)
|
||||||
|
test_2(arg.1, otherarg.2)
|
||||||
|
test_1(arg.2)
|
||||||
|
test_2(arg.2, otherarg.1)
|
||||||
|
test_2(arg.2, otherarg.2)
|
||||||
|
|
||||||
|
Moreover, test_2(arg.1) will execute any registered teardowns for
|
||||||
|
the arg.1 resource after the test finished execution.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
XXX it's quite unclear at the moment how to implement.
|
||||||
|
If we have a 1000 tests requiring different sets of parametrized
|
||||||
|
resources with different scopes, how to re-order accordingly?
|
||||||
|
It even seems difficult to express the expectation in a
|
||||||
|
concise manner.
|
||||||
|
|
||||||
- allow specifying dependencies in the funcarg-marker
|
|
||||||
- allow funcargs for factories as well
|
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -24,7 +24,7 @@ def main():
|
||||||
name='pytest',
|
name='pytest',
|
||||||
description='py.test: simple powerful testing with Python',
|
description='py.test: simple powerful testing with Python',
|
||||||
long_description = long_description,
|
long_description = long_description,
|
||||||
version='2.3.0.dev3',
|
version='2.3.0.dev4',
|
||||||
url='http://pytest.org',
|
url='http://pytest.org',
|
||||||
license='MIT license',
|
license='MIT license',
|
||||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||||
|
|
|
@ -1746,12 +1746,121 @@ class TestFuncargManager:
|
||||||
reprec = testdir.inline_run("-s")
|
reprec = testdir.inline_run("-s")
|
||||||
reprec.assertoutcome(passed=1)
|
reprec.assertoutcome(passed=1)
|
||||||
|
|
||||||
|
class TestSetupDiscovery:
|
||||||
|
def pytest_funcarg__testdir(self, request):
|
||||||
|
testdir = request.getfuncargvalue("testdir")
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import pytest
|
||||||
|
@pytest.mark.setup
|
||||||
|
def perfunction(request):
|
||||||
|
pass
|
||||||
|
@pytest.mark.setup
|
||||||
|
def perfunction2(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pytest_funcarg__fm(request):
|
||||||
|
return request.funcargmanager
|
||||||
|
|
||||||
|
def pytest_funcarg__item(request):
|
||||||
|
return request._pyfuncitem
|
||||||
|
""")
|
||||||
|
return testdir
|
||||||
|
|
||||||
|
def test_parsefactories_conftest(self, testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
def test_check_setup(item, fm):
|
||||||
|
setuplist, allnames = fm.getsetuplist(item.nodeid)
|
||||||
|
assert len(setuplist) == 2
|
||||||
|
assert setuplist[0][0].__name__ == "perfunction"
|
||||||
|
assert "request" in setuplist[0][1]
|
||||||
|
assert setuplist[1][0].__name__ == "perfunction2"
|
||||||
|
assert "request" in setuplist[1][1]
|
||||||
|
""")
|
||||||
|
reprec = testdir.inline_run("-s")
|
||||||
|
reprec.assertoutcome(passed=1)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSetupManagement:
|
||||||
|
def test_funcarg_and_setup(self, testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
l = []
|
||||||
|
@pytest.mark.funcarg(scope="module")
|
||||||
|
def arg(request):
|
||||||
|
l.append(1)
|
||||||
|
return 0
|
||||||
|
@pytest.mark.setup(scope="class")
|
||||||
|
def something(request, arg):
|
||||||
|
l.append(2)
|
||||||
|
|
||||||
|
def test_hello(arg):
|
||||||
|
assert len(l) == 2
|
||||||
|
assert l == [1,2]
|
||||||
|
assert arg == 0
|
||||||
|
|
||||||
|
def test_hello2(arg):
|
||||||
|
assert len(l) == 2
|
||||||
|
assert l == [1,2]
|
||||||
|
assert arg == 0
|
||||||
|
""")
|
||||||
|
reprec = testdir.inline_run()
|
||||||
|
reprec.assertoutcome(passed=2)
|
||||||
|
|
||||||
|
def test_setup_uses_parametrized_resource(self, testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
l = []
|
||||||
|
@pytest.mark.funcarg(params=[1,2])
|
||||||
|
def arg(request):
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
@pytest.mark.setup
|
||||||
|
def something(request, arg):
|
||||||
|
l.append(arg)
|
||||||
|
|
||||||
|
def test_hello():
|
||||||
|
if len(l) == 1:
|
||||||
|
assert l == [1]
|
||||||
|
elif len(l) == 2:
|
||||||
|
assert l == [1, 2]
|
||||||
|
else:
|
||||||
|
0/0
|
||||||
|
|
||||||
|
""")
|
||||||
|
reprec = testdir.inline_run("-s")
|
||||||
|
reprec.assertoutcome(passed=2)
|
||||||
|
|
||||||
|
def test_session_parametrized_function_setup(self, testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
l = []
|
||||||
|
|
||||||
|
@pytest.mark.funcarg(scope="session", params=[1,2])
|
||||||
|
def arg(request):
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
@pytest.mark.setup(scope="function")
|
||||||
|
def append(request, arg):
|
||||||
|
if request.function.__name__ == "test_some":
|
||||||
|
l.append(arg)
|
||||||
|
|
||||||
|
def test_some():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_result(arg):
|
||||||
|
assert len(l) == 2
|
||||||
|
assert l == [1,2]
|
||||||
|
""")
|
||||||
|
reprec = testdir.inline_run("-s")
|
||||||
|
reprec.assertoutcome(passed=4)
|
||||||
|
|
||||||
class TestFuncargMarker:
|
class TestFuncargMarker:
|
||||||
def test_parametrize(self, testdir):
|
def test_parametrize(self, testdir):
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.mark.funcarg(params=["a", "b", "c"])
|
@pytest.mark.funcarg(params=["a", "b", "c"])
|
||||||
def pytest_funcarg__arg(request):
|
def arg(request):
|
||||||
return request.param
|
return request.param
|
||||||
l = []
|
l = []
|
||||||
def test_param(arg):
|
def test_param(arg):
|
||||||
|
@ -1767,7 +1876,7 @@ class TestFuncargMarker:
|
||||||
import pytest
|
import pytest
|
||||||
l = []
|
l = []
|
||||||
@pytest.mark.funcarg(scope="module")
|
@pytest.mark.funcarg(scope="module")
|
||||||
def pytest_funcarg__arg(request):
|
def arg(request):
|
||||||
l.append(1)
|
l.append(1)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
@ -1789,7 +1898,7 @@ class TestFuncargMarker:
|
||||||
import pytest
|
import pytest
|
||||||
l = []
|
l = []
|
||||||
@pytest.mark.funcarg(scope="module")
|
@pytest.mark.funcarg(scope="module")
|
||||||
def pytest_funcarg__arg(request):
|
def arg(request):
|
||||||
l.append(1)
|
l.append(1)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
@ -1812,7 +1921,7 @@ class TestFuncargMarker:
|
||||||
finalized = []
|
finalized = []
|
||||||
created = []
|
created = []
|
||||||
@pytest.mark.funcarg(scope="module")
|
@pytest.mark.funcarg(scope="module")
|
||||||
def pytest_funcarg__arg(request):
|
def arg(request):
|
||||||
created.append(1)
|
created.append(1)
|
||||||
assert request.scope == "module"
|
assert request.scope == "module"
|
||||||
request.addfinalizer(lambda: finalized.append(1))
|
request.addfinalizer(lambda: finalized.append(1))
|
||||||
|
@ -1851,14 +1960,14 @@ class TestFuncargMarker:
|
||||||
finalized = []
|
finalized = []
|
||||||
created = []
|
created = []
|
||||||
@pytest.mark.funcarg(scope="function")
|
@pytest.mark.funcarg(scope="function")
|
||||||
def pytest_funcarg__arg(request):
|
def arg(request):
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
test_mod1="""
|
test_mod1="""
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.mark.funcarg(scope="session")
|
@pytest.mark.funcarg(scope="session")
|
||||||
def pytest_funcarg__arg(request):
|
def arg(request):
|
||||||
%s
|
%s
|
||||||
def test_1(arg):
|
def test_1(arg):
|
||||||
pass
|
pass
|
||||||
|
@ -1894,7 +2003,7 @@ class TestFuncargMarker:
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.mark.funcarg(scope="module", params=["a", "b", "c"])
|
@pytest.mark.funcarg(scope="module", params=["a", "b", "c"])
|
||||||
def pytest_funcarg__arg(request):
|
def arg(request):
|
||||||
return request.param
|
return request.param
|
||||||
l = []
|
l = []
|
||||||
def test_param(arg):
|
def test_param(arg):
|
||||||
|
|
Loading…
Reference in New Issue