drops special testcontext object in favour of "old" request object, simplifying communication and code for the 2.2-2.3 transition. also modify docs and examples.
This commit is contained in:
parent
f6b555f5ad
commit
646c2c6001
16
CHANGELOG
16
CHANGELOG
|
@ -9,19 +9,9 @@ Changes between 2.2.4 and 2.3.0.dev
|
|||
it is constructed correctly from the original current working dir.
|
||||
- fix "python setup.py test" example to cause a proper "errno" return
|
||||
- fix issue165 - fix broken doc links and mention stackoverflow for FAQ
|
||||
- fix issue139 - merge FuncargRequest and Item API such that
|
||||
funcarg-functionality is now directly available on the "item"
|
||||
object passed to the various pytest_runtest hooks. This allows more
|
||||
sensitive behaviour of e.g. the pytest-django plugin which previously
|
||||
had no full access to all instantiated funcargs.
|
||||
This internal API re-organisation is a fully backward compatible
|
||||
change: existing factories accepting a "request" object will
|
||||
get a Function "item" object which carries the same API. In fact,
|
||||
the FuncargRequest API (or rather then a ResourceRequestAPI)
|
||||
could be available for all collection and item nodes but this is
|
||||
left for later consideration because it would render the documentation
|
||||
invalid and the "funcarg" naming sounds odd in context of
|
||||
directory, file, class, etc. nodes.
|
||||
- fix issue139 - introduce @pytest.factory which allows direct scoping
|
||||
and parametrization of funcarg factories. Introduce new @pytest.setup
|
||||
marker to allow the writing of setup functions which accept funcargs.
|
||||
- catch unicode-issues when writing failure representations
|
||||
to terminal to prevent the whole session from crashing
|
||||
- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip
|
||||
|
|
|
@ -912,31 +912,51 @@ class Function(FunctionMixin, pytest.Item):
|
|||
def __hash__(self):
|
||||
return hash((self.parent, self.name))
|
||||
|
||||
scope2props = dict(session=())
|
||||
scope2props["module"] = ("fspath", "module")
|
||||
scope2props["class"] = scope2props["module"] + ("cls",)
|
||||
scope2props["instance"] = scope2props["class"] + ("instance", )
|
||||
scope2props["function"] = scope2props["instance"] + ("function", "keywords")
|
||||
|
||||
def scopeproperty(name=None, doc=None):
|
||||
def decoratescope(func):
|
||||
scopename = name or func.__name__
|
||||
def provide(self):
|
||||
if func.__name__ in scope2props[self.scope]:
|
||||
return func(self)
|
||||
raise AttributeError("%s not available in %s-scoped context" % (
|
||||
scopename, self.scope))
|
||||
return property(provide, None, None, func.__doc__)
|
||||
return decoratescope
|
||||
|
||||
def pytest_funcarg__request(__request__):
|
||||
return __request__
|
||||
|
||||
#def pytest_funcarg__testcontext(__request__):
|
||||
# return __request__
|
||||
|
||||
class FuncargRequest:
|
||||
""" (old-style) A request for function arguments from a test function.
|
||||
""" A request for function arguments from a test or setup function.
|
||||
|
||||
Note that there is an optional ``param`` attribute in case
|
||||
there was an invocation to metafunc.addcall(param=...).
|
||||
If no such call was done in a ``pytest_generate_tests``
|
||||
hook, the attribute will not be present. Note that
|
||||
as of pytest-2.3 you probably rather want to use the
|
||||
testcontext object and mark your factory with a ``@pytest.factory``
|
||||
marker.
|
||||
A request object gives access to attributes of the requesting
|
||||
test context. It has an optional ``param`` attribute in case
|
||||
of parametrization.
|
||||
"""
|
||||
|
||||
def __init__(self, pyfuncitem):
|
||||
self._pyfuncitem = pyfuncitem
|
||||
if hasattr(pyfuncitem, '_requestparam'):
|
||||
self.param = pyfuncitem._requestparam
|
||||
#: Scope string, one of "function", "cls", "module", "session"
|
||||
self.scope = "function"
|
||||
self.getparent = pyfuncitem.getparent
|
||||
self._funcargs = self._pyfuncitem.funcargs.copy()
|
||||
self._funcargs["__request__"] = self
|
||||
self._name2factory = {}
|
||||
self.funcargmanager = pyfuncitem.session.funcargmanager
|
||||
self._currentarg = None
|
||||
self.funcargnames = getfuncargnames(self.function)
|
||||
self.parentid = pyfuncitem.parent.nodeid
|
||||
self.scope = "function"
|
||||
self._factorystack = []
|
||||
|
||||
def _getfaclist(self, argname):
|
||||
|
@ -956,21 +976,42 @@ class FuncargRequest:
|
|||
self.parentid)
|
||||
return facdeflist
|
||||
|
||||
def raiseerror(self, msg):
|
||||
""" raise a FuncargLookupError with the given message. """
|
||||
raise self.funcargmanager.FuncargLookupError(self.function, msg)
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
""" the pytest config object associated with this request. """
|
||||
return self._pyfuncitem.config
|
||||
|
||||
|
||||
@scopeproperty()
|
||||
def function(self):
|
||||
""" function object of the test invocation. """
|
||||
""" test function object if the request has a per-function scope. """
|
||||
return self._pyfuncitem.obj
|
||||
|
||||
@scopeproperty("class")
|
||||
def cls(self):
|
||||
""" class (can be None) where the test function was collected. """
|
||||
clscol = self._pyfuncitem.getparent(pytest.Class)
|
||||
if clscol:
|
||||
return clscol.obj
|
||||
|
||||
@scopeproperty()
|
||||
def instance(self):
|
||||
""" instance (can be None) on which test function was collected. """
|
||||
return py.builtin._getimself(self.function)
|
||||
|
||||
@scopeproperty()
|
||||
def module(self):
|
||||
""" python module object where the test function was collected. """
|
||||
return self._pyfuncitem.getparent(pytest.Module).obj
|
||||
|
||||
@scopeproperty()
|
||||
def fspath(self):
|
||||
""" the file system path of the test module which collected this test. """
|
||||
return self._pyfuncitem.fspath
|
||||
|
||||
@property
|
||||
def keywords(self):
|
||||
""" keywords of the test function item.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
""" keywords of the test function item. """
|
||||
return self._pyfuncitem.keywords
|
||||
|
||||
@property
|
||||
|
@ -978,41 +1019,23 @@ class FuncargRequest:
|
|||
""" pytest session object. """
|
||||
return self._pyfuncitem.session
|
||||
|
||||
@property
|
||||
def module(self):
|
||||
""" module where the test function was collected. """
|
||||
return self._pyfuncitem.getparent(pytest.Module).obj
|
||||
|
||||
@property
|
||||
def cls(self):
|
||||
""" class (can be None) where the test function was collected. """
|
||||
clscol = self._pyfuncitem.getparent(pytest.Class)
|
||||
if clscol:
|
||||
return clscol.obj
|
||||
@property
|
||||
def instance(self):
|
||||
""" instance (can be None) on which test function was collected. """
|
||||
return py.builtin._getimself(self.function)
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
""" the pytest config object associated with this request. """
|
||||
return self._pyfuncitem.config
|
||||
def addfinalizer(self, finalizer):
|
||||
"""add finalizer/teardown function to be called after the
|
||||
last test within the requesting test context finished
|
||||
execution. """
|
||||
self._addfinalizer(finalizer, scope=self.scope)
|
||||
|
||||
@property
|
||||
def fspath(self):
|
||||
""" the file system path of the test module which collected this test. """
|
||||
return self._pyfuncitem.fspath
|
||||
|
||||
def _fillfuncargs(self):
|
||||
if self.funcargnames:
|
||||
assert not getattr(self._pyfuncitem, '_args', None), (
|
||||
"yielded functions cannot have funcargs")
|
||||
while self.funcargnames:
|
||||
argname = self.funcargnames.pop(0)
|
||||
if argname not in self._pyfuncitem.funcargs:
|
||||
self._pyfuncitem.funcargs[argname] = \
|
||||
self.getfuncargvalue(argname)
|
||||
def _addfinalizer(self, finalizer, scope):
|
||||
if scope != "function" and hasattr(self, "param"):
|
||||
# parametrized resources are sorted by param
|
||||
# so we rather store finalizers per (argname, param)
|
||||
colitem = (self._currentarg, self.param)
|
||||
else:
|
||||
colitem = self._getscopeitem(scope)
|
||||
self._pyfuncitem.session._setupstate.addfinalizer(
|
||||
finalizer=finalizer, colitem=colitem)
|
||||
|
||||
def applymarker(self, marker):
|
||||
""" Apply a marker to a single test function invocation.
|
||||
|
@ -1026,11 +1049,33 @@ class FuncargRequest:
|
|||
raise ValueError("%r is not a py.test.mark.* object")
|
||||
self._pyfuncitem.keywords[marker.markname] = marker
|
||||
|
||||
def raiseerror(self, msg):
|
||||
""" raise a FuncargLookupError with the given message. """
|
||||
raise self.funcargmanager.FuncargLookupError(self.function, msg)
|
||||
|
||||
|
||||
def _fillfuncargs(self):
|
||||
if self.funcargnames:
|
||||
assert not getattr(self._pyfuncitem, '_args', None), (
|
||||
"yielded functions cannot have funcargs")
|
||||
while self.funcargnames:
|
||||
argname = self.funcargnames.pop(0)
|
||||
if argname not in self._pyfuncitem.funcargs:
|
||||
self._pyfuncitem.funcargs[argname] = \
|
||||
self.getfuncargvalue(argname)
|
||||
|
||||
|
||||
def _callsetup(self):
|
||||
self.funcargmanager.ensure_setupcalls(self)
|
||||
|
||||
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
|
||||
""" Return a testing resource managed by ``setup`` &
|
||||
""" (deprecated) Return a testing resource managed by ``setup`` &
|
||||
``teardown`` calls. ``scope`` and ``extrakey`` determine when the
|
||||
``teardown`` function will be called so that subsequent calls to
|
||||
``setup`` would recreate the resource.
|
||||
``setup`` would recreate the resource. With pytest-2.3 you
|
||||
do not need ``cached_setup()`` as you can directly declare a scope
|
||||
on a funcarg factory and register a finalizer through
|
||||
``request.addfinalizer()``.
|
||||
|
||||
:arg teardown: function receiving a previously setup resource.
|
||||
:arg setup: a no-argument function creating a resource.
|
||||
|
@ -1061,16 +1106,19 @@ class FuncargRequest:
|
|||
return val
|
||||
|
||||
|
||||
def _callsetup(self):
|
||||
self.funcargmanager.ensure_setupcalls(self)
|
||||
|
||||
def getfuncargvalue(self, argname):
|
||||
""" Retrieve a function argument by name for this test
|
||||
""" (deprecated) Retrieve a function argument by name for this test
|
||||
function invocation. This allows one function argument factory
|
||||
to call another function argument factory. If there are two
|
||||
funcarg factories for the same test function argument the first
|
||||
factory may use ``getfuncargvalue`` to call the second one and
|
||||
do something additional with the resource.
|
||||
|
||||
**Note**, however, that starting with
|
||||
pytest-2.3 it is easier and better to directly state the needed
|
||||
funcarg in the factory signature. This will also work seemlessly
|
||||
with parametrization and the new resource setup optimizations.
|
||||
"""
|
||||
try:
|
||||
return self._funcargs[argname]
|
||||
|
@ -1091,11 +1139,6 @@ class FuncargRequest:
|
|||
factory_kwargs = {}
|
||||
def fillfactoryargs():
|
||||
for newname in newnames:
|
||||
if newname == "testcontext":
|
||||
val = TestContextResource(self)
|
||||
elif newname == "request" and not factorydef.new:
|
||||
val = self
|
||||
else:
|
||||
val = self.getfuncargvalue(newname)
|
||||
factory_kwargs[newname] = val
|
||||
|
||||
|
@ -1161,21 +1204,6 @@ class FuncargRequest:
|
|||
return self._pyfuncitem.getparent(pytest.Module)
|
||||
raise ValueError("unknown finalization scope %r" %(scope,))
|
||||
|
||||
def addfinalizer(self, finalizer):
|
||||
"""add finalizer function to be called after test function
|
||||
finished execution. """
|
||||
self._addfinalizer(finalizer, scope=self.scope)
|
||||
|
||||
def _addfinalizer(self, finalizer, scope):
|
||||
if scope != "function" and hasattr(self, "param"):
|
||||
# parametrized resources are sorted by param
|
||||
# so we rather store finalizers per (argname, param)
|
||||
colitem = (self._currentarg, self.param)
|
||||
else:
|
||||
colitem = self._getscopeitem(scope)
|
||||
self._pyfuncitem.session._setupstate.addfinalizer(
|
||||
finalizer=finalizer, colitem=colitem)
|
||||
|
||||
def __repr__(self):
|
||||
return "<FuncargRequest for %r>" %(self._pyfuncitem)
|
||||
|
||||
|
@ -1396,17 +1424,13 @@ class FuncargManager:
|
|||
if setupcall.active:
|
||||
continue
|
||||
request._factorystack.append(setupcall)
|
||||
mp = monkeypatch()
|
||||
try:
|
||||
testcontext = TestContextSetup(request, setupcall)
|
||||
#mp.setattr(request, "_setupcall", setupcall, raising=False)
|
||||
mp.setattr(request, "scope", setupcall.scope)
|
||||
kwargs = {}
|
||||
for name in setupcall.funcargnames:
|
||||
try:
|
||||
kwargs[name] = request.getfuncargvalue(name)
|
||||
except FuncargLookupError:
|
||||
if name == "testcontext":
|
||||
kwargs[name] = testcontext
|
||||
else:
|
||||
raise
|
||||
scope = setupcall.scope or "function"
|
||||
scol = setupcall.scopeitem = request._getscopeitem(scope)
|
||||
self.session._setupstate.addfinalizer(setupcall.finish, scol)
|
||||
|
@ -1414,6 +1438,7 @@ class FuncargManager:
|
|||
self.addargfinalizer(setupcall.finish, argname)
|
||||
setupcall.execute(kwargs)
|
||||
finally:
|
||||
mp.undo()
|
||||
request._factorystack.remove(setupcall)
|
||||
|
||||
def addargfinalizer(self, finalizer, argname):
|
||||
|
@ -1427,69 +1452,6 @@ class FuncargManager:
|
|||
except ValueError:
|
||||
pass
|
||||
|
||||
scope2props = dict(session=())
|
||||
scope2props["module"] = ("fspath", "module")
|
||||
scope2props["class"] = scope2props["module"] + ("cls",)
|
||||
scope2props["function"] = scope2props["class"] + ("function", "keywords")
|
||||
|
||||
def scopeprop(attr, name=None, doc=None):
|
||||
if doc is None:
|
||||
doc = ("%s of underlying test context, may not exist "
|
||||
"if the testcontext has a higher scope" % (attr,))
|
||||
name = name or attr
|
||||
def get(self):
|
||||
if name in scope2props[self.scope]:
|
||||
return getattr(self._request, name)
|
||||
raise AttributeError("%s not available in %s-scoped context" % (
|
||||
name, self.scope))
|
||||
return property(get, doc=doc)
|
||||
|
||||
def rprop(attr, doc=None):
|
||||
if doc is None:
|
||||
doc = "%s of underlying test context" % attr
|
||||
return property(lambda x: getattr(x._request, attr), doc=doc)
|
||||
|
||||
class TestContext(object):
|
||||
""" Basic objects of the current testing context. """
|
||||
def __init__(self, request, scope):
|
||||
self._request = request
|
||||
self.scope = scope
|
||||
|
||||
# no getfuncargvalue(), cached_setup, applymarker helpers here
|
||||
# on purpose
|
||||
|
||||
config = rprop("config", "pytest config object.")
|
||||
session = rprop("session", "pytest session object.")
|
||||
|
||||
function = scopeprop("function")
|
||||
module = scopeprop("module")
|
||||
cls = scopeprop("class", "cls")
|
||||
instance = scopeprop("instance")
|
||||
fspath = scopeprop("fspath")
|
||||
#keywords = scopeprop("keywords")
|
||||
|
||||
class TestContextSetup(TestContext):
|
||||
def __init__(self, request, setupcall):
|
||||
self._setupcall = setupcall
|
||||
self._finalizers = []
|
||||
super(TestContextSetup, self).__init__(request, setupcall.scope)
|
||||
|
||||
def addfinalizer(self, finalizer):
|
||||
""" Add a finalizer to be called after the last test in the
|
||||
test context executes. """
|
||||
self._setupcall.addfinalizer(finalizer)
|
||||
|
||||
class TestContextResource(TestContext):
|
||||
param = rprop("param")
|
||||
|
||||
def __init__(self, request):
|
||||
super(TestContextResource, self).__init__(request, request.scope)
|
||||
|
||||
def addfinalizer(self, finalizer):
|
||||
""" Add a finalizer to be called after the last test in the
|
||||
test context executes. """
|
||||
self._request.addfinalizer(finalizer)
|
||||
|
||||
|
||||
class SetupCall:
|
||||
""" a container/helper for managing calls to setup functions. """
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
import pytest
|
||||
|
||||
@pytest.factory("session")
|
||||
def setup(testcontext):
|
||||
def setup(request):
|
||||
setup = CostlySetup()
|
||||
testcontext.addfinalizer(setup.finalize)
|
||||
request.addfinalizer(setup.finalize)
|
||||
return setup
|
||||
|
||||
class CostlySetup:
|
||||
|
|
|
@ -6,13 +6,13 @@ import py, pytest
|
|||
|
||||
pythonlist = ['python2.4', 'python2.5', 'python2.6', 'python2.7', 'python2.8']
|
||||
@pytest.factory(params=pythonlist)
|
||||
def python1(testcontext, tmpdir):
|
||||
def python1(request, tmpdir):
|
||||
picklefile = tmpdir.join("data.pickle")
|
||||
return Python(testcontext.param, picklefile)
|
||||
return Python(request.param, picklefile)
|
||||
|
||||
@pytest.factory(params=pythonlist)
|
||||
def python2(testcontext, python1):
|
||||
return Python(testcontext.param, python1.picklefile)
|
||||
def python2(request, python1):
|
||||
return Python(request.param, python1.picklefile)
|
||||
|
||||
class Python:
|
||||
def __init__(self, version, picklefile):
|
||||
|
|
|
@ -66,15 +66,15 @@ Direct scoping of funcarg factories
|
|||
Instead of calling cached_setup(), you can use the :ref:`@pytest.factory <@pytest.factory>` decorator and directly state the scope::
|
||||
|
||||
@pytest.factory(scope="session")
|
||||
def db(testcontext):
|
||||
def db(request):
|
||||
# factory will only be invoked once per session -
|
||||
db = DataBase()
|
||||
testcontext.addfinalizer(db.destroy) # destroy when session is finished
|
||||
request.addfinalizer(db.destroy) # destroy when session is finished
|
||||
return db
|
||||
|
||||
This factory implementation does not need to call ``cached_setup()`` anymore
|
||||
because it will only be invoked once per session. Moreover, the
|
||||
``testcontext.addfinalizer()`` registers a finalizer according to the specified
|
||||
``request.addfinalizer()`` registers a finalizer according to the specified
|
||||
resource scope on which the factory function is operating.
|
||||
|
||||
|
||||
|
@ -88,29 +88,29 @@ parametrization, i.e. calling a test multiple times with different value
|
|||
sets. pytest-2.3 introduces a decorator for use on the factory itself::
|
||||
|
||||
@pytest.factory(params=["mysql", "pg"])
|
||||
def db(testcontext):
|
||||
... # use testcontext.param
|
||||
def db(request):
|
||||
... # use request.param
|
||||
|
||||
Here the factory will be invoked twice (with the respective "mysql"
|
||||
and "pg" values set as ``testcontext.param`` attributes) and and all of
|
||||
and "pg" values set as ``request.param`` attributes) and and all of
|
||||
the tests requiring "db" will run twice as well. The "mysql" and
|
||||
"pg" values will also be used for reporting the test-invocation variants.
|
||||
|
||||
This new way of parametrizing funcarg factories should in many cases
|
||||
allow to re-use already written factories because effectively
|
||||
``testcontext.param`` are already the parametrization attribute for test
|
||||
``request.param`` are already the parametrization attribute for test
|
||||
functions/classes were parametrized via
|
||||
:py:func:`~_pytest.python.Metafunc.parametrize(indirect=True)` calls.
|
||||
|
||||
Of course it's perfectly fine to combine parametrization and scoping::
|
||||
|
||||
@pytest.factory(scope="session", params=["mysql", "pg"])
|
||||
def db(testcontext):
|
||||
if testcontext.param == "mysql":
|
||||
def db(request):
|
||||
if request.param == "mysql":
|
||||
db = MySQL()
|
||||
elif testcontext.param == "pg":
|
||||
elif request.param == "pg":
|
||||
db = PG()
|
||||
testcontext.addfinalizer(db.destroy) # destroy when session is finished
|
||||
request.addfinalizer(db.destroy) # destroy when session is finished
|
||||
return db
|
||||
|
||||
This would execute all tests requiring the per-session "db" resource twice,
|
||||
|
@ -126,7 +126,7 @@ denotes the name under which the resource can be accessed as a function
|
|||
argument::
|
||||
|
||||
@pytest.factory()
|
||||
def db(testcontext):
|
||||
def db(request):
|
||||
...
|
||||
|
||||
The name under which the funcarg resource can be requested is ``db``.
|
||||
|
|
|
@ -9,44 +9,45 @@ funcargs: resource injection and parametrization
|
|||
|
||||
.. note::
|
||||
|
||||
pytest-2.3 introduces major refinements to the original funcarg
|
||||
mechanism introduced to pytest-2.0. While the old way
|
||||
remains fully supported, it is recommended to use the refined
|
||||
mechanisms. See also the `compatibility notes`_ and the detailed
|
||||
:ref:`reasoning for the new funcarg and setup functions <funcargcompare>`.
|
||||
pytest-2.3 introduces major refinements to the test setup and funcarg
|
||||
mechanisms introduced to pytest-2.0. All pre-2.3 usages remain
|
||||
supported and several use cases, among them scoping and parametrization
|
||||
of funcarg resources, are now easier to accomplish. For more background,
|
||||
see `compatibility notes`_ and the detailed :ref:`reasoning for the new
|
||||
funcarg and setup functions <funcargcompare>`.
|
||||
|
||||
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection
|
||||
|
||||
Introduction
|
||||
====================
|
||||
|
||||
py.test supports the injection of objects into test and setup functions
|
||||
pytest supports the injection of test resources into test and setup functions
|
||||
and flexibly control their life cycle in relation to the overall test
|
||||
execution. Moreover, you can run a test function multiple times
|
||||
injecting different objects.
|
||||
execution. Moreover, tests can get executed multiple times if you have
|
||||
different variants of test resources to test with.
|
||||
|
||||
The basic mechanism for injecting objects is called the *funcarg
|
||||
mechanism* because objects are **injected** when a test or setup
|
||||
function states it as an **argument**. The injected argument is
|
||||
created by a call to a registered **funcarg factory**. This approach is
|
||||
an example of `Dependency Injection`_ and helps to de-couple test code
|
||||
from the setup of required objects: at test writing time you do not need
|
||||
to care for the details of where and how your required resources are
|
||||
constructed, if they are shared on a per-class, module or session basis,
|
||||
or if your test function is invoked multiple times with differently
|
||||
configured resource instances.
|
||||
mechanism* because objects are injected when a test or setup
|
||||
**function** states it as an **argument**. The injected argument
|
||||
is created by a call to a registered **funcarg factory** for each argument
|
||||
name. This mechanism is an example of `Dependency Injection`_
|
||||
and helps to de-couple test code from the setup of required
|
||||
objects: at test writing time you do not need to care for the details of
|
||||
where and how your required test resources are constructed, if they are
|
||||
shared on a per-class, module or session basis, or if your test function
|
||||
is invoked multiple times with differently configured resource
|
||||
instances.
|
||||
|
||||
Funcarg dependency injection allows to organise test resources
|
||||
in a modular explicit way so that test functions state their needs
|
||||
in their signature. pytest additionally offers powerful xunit-style
|
||||
:ref:`setup functions <setup functions>` for the cases where you need
|
||||
to create implicit test state that is not passed explicitely to test functions.
|
||||
|
||||
When a test function is invoked multiple times with different arguments we
|
||||
speak of **parametrized testing**. This is useful if you want to test
|
||||
e.g. against different database backends or want to write a parametrized
|
||||
test function, checking that certain inputs lead to certain outputs.
|
||||
You can parametrize funcarg factories, parametrize test function
|
||||
arguments or even implement your own parametrization scheme through a
|
||||
plugin hook.
|
||||
|
||||
pytest additionally offers powerful xunit-style :ref:`setup functions <setup
|
||||
functions>` for the cases where you need to create implicit test state
|
||||
that is not passed explicitely to test functions.
|
||||
speak of **parametrized testing**. You can use it e. g. to repeatedly run test
|
||||
functions against different database backends or to check that certain
|
||||
inputs lead to certain outputs.
|
||||
|
||||
Concretely, there are three main means of funcarg management:
|
||||
|
||||
|
@ -54,7 +55,7 @@ Concretely, there are three main means of funcarg management:
|
|||
their scoping and parametrization. Factories can themselves
|
||||
receive resources through their function arguments, easing
|
||||
the setup of `interdependent resources`_. Factories can use
|
||||
the special `testcontext`_ object to access details from where
|
||||
the special `request`_ object to access details from where
|
||||
the factory or setup function is called and for registering finalizers.
|
||||
|
||||
* a `@pytest.mark.parametrize`_ marker for executing test functions
|
||||
|
@ -103,8 +104,8 @@ factory function. Running the tests looks like this::
|
|||
|
||||
$ py.test test_simplefactory.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8
|
||||
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
|
||||
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
||||
collecting ... collected 1 items
|
||||
|
||||
test_simplefactory.py F
|
||||
|
@ -119,7 +120,7 @@ factory function. Running the tests looks like this::
|
|||
E assert 42 == 17
|
||||
|
||||
test_simplefactory.py:8: AssertionError
|
||||
========================= 1 failed in 0.02 seconds =========================
|
||||
========================= 1 failed in 0.01 seconds =========================
|
||||
|
||||
This shows that the test function was called with a ``myfuncarg``
|
||||
argument value of ``42`` and the assert fails as expected. Here is
|
||||
|
@ -186,7 +187,7 @@ test session::
|
|||
import smtplib
|
||||
|
||||
@pytest.factory(scope="session")
|
||||
def smtp(testcontext):
|
||||
def smtp(request):
|
||||
return smtplib.SMTP("merlinux.eu")
|
||||
|
||||
The name of the factory is ``smtp`` (the factory function name)
|
||||
|
@ -214,7 +215,7 @@ inspect what is going on and can now run the tests::
|
|||
================================= FAILURES =================================
|
||||
________________________________ test_ehlo _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x25e7b48>
|
||||
smtp = <smtplib.SMTP instance at 0x31bce18>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
|
@ -226,7 +227,7 @@ inspect what is going on and can now run the tests::
|
|||
test_module.py:5: AssertionError
|
||||
________________________________ test_noop _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x25e7b48>
|
||||
smtp = <smtplib.SMTP instance at 0x31bce18>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
|
@ -235,7 +236,7 @@ inspect what is going on and can now run the tests::
|
|||
E assert 0
|
||||
|
||||
test_module.py:10: AssertionError
|
||||
2 failed in 0.20 seconds
|
||||
2 failed in 0.26 seconds
|
||||
|
||||
you see the two ``assert 0`` failing and can also see that
|
||||
the same (session-scoped) object was passed into the two test functions.
|
||||
|
@ -247,7 +248,7 @@ Parametrizing a session-shared funcarg resource
|
|||
Extending the previous example, we can flag the factory to create
|
||||
two ``smtp`` values which will cause all tests using it to
|
||||
run twice with two different values. The factory function gets
|
||||
access to each parameter through the special `testcontext`_ object::
|
||||
access to each parameter through the special `request`_ object::
|
||||
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
|
@ -255,11 +256,11 @@ access to each parameter through the special `testcontext`_ object::
|
|||
|
||||
@pytest.factory(scope="session",
|
||||
params=["merlinux.eu", "mail.python.org"])
|
||||
def smtp(testcontext):
|
||||
return smtplib.SMTP(testcontext.param)
|
||||
def smtp(request):
|
||||
return smtplib.SMTP(request.param)
|
||||
|
||||
The main change is the definition of a ``params`` list in the
|
||||
``factory``-marker and the ``testcontext.param`` access within the
|
||||
``factory``-marker and the ``request.param`` access within the
|
||||
factory function. No test function code needs to change.
|
||||
So let's just do another run::
|
||||
|
||||
|
@ -269,7 +270,7 @@ So let's just do another run::
|
|||
================================= FAILURES =================================
|
||||
__________________________ test_ehlo[merlinux.eu] __________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x2960050>
|
||||
smtp = <smtplib.SMTP instance at 0x28dc5a8>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
|
@ -281,7 +282,7 @@ So let's just do another run::
|
|||
test_module.py:5: AssertionError
|
||||
__________________________ test_noop[merlinux.eu] __________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x2960050>
|
||||
smtp = <smtplib.SMTP instance at 0x28dc5a8>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
|
@ -292,7 +293,7 @@ So let's just do another run::
|
|||
test_module.py:10: AssertionError
|
||||
________________________ test_ehlo[mail.python.org] ________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x296a128>
|
||||
smtp = <smtplib.SMTP instance at 0x28e3e18>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
|
@ -303,7 +304,7 @@ So let's just do another run::
|
|||
test_module.py:4: AssertionError
|
||||
________________________ test_noop[mail.python.org] ________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x296a128>
|
||||
smtp = <smtplib.SMTP instance at 0x28e3e18>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
|
@ -312,7 +313,7 @@ So let's just do another run::
|
|||
E assert 0
|
||||
|
||||
test_module.py:10: AssertionError
|
||||
4 failed in 6.00 seconds
|
||||
4 failed in 6.17 seconds
|
||||
|
||||
We get four failures because we are running the two tests twice with
|
||||
different ``smtp`` instantiations as defined on the factory.
|
||||
|
@ -324,7 +325,7 @@ Adding a finalizer to the parametrized resource
|
|||
|
||||
Further extending the ``smtp`` example, we now want to properly
|
||||
close a smtp server connection after the last test using it
|
||||
has been run. We can do this by calling the ``testcontext.addfinalizer()``
|
||||
has been run. We can do this by calling the ``request.addfinalizer()``
|
||||
helper::
|
||||
|
||||
# content of conftest.py
|
||||
|
@ -333,12 +334,12 @@ helper::
|
|||
|
||||
@pytest.factory(scope="session",
|
||||
params=["merlinux.eu", "mail.python.org"])
|
||||
def smtp(testcontext):
|
||||
smtp = smtplib.SMTP(testcontext.param)
|
||||
def smtp(request):
|
||||
smtp = smtplib.SMTP(request.param)
|
||||
def fin():
|
||||
print ("finalizing %s" % smtp)
|
||||
smtp.close()
|
||||
testcontext.addfinalizer(fin)
|
||||
request.addfinalizer(fin)
|
||||
return smtp
|
||||
|
||||
We also add a print call and then run py.test without the default
|
||||
|
@ -347,9 +348,9 @@ output capturing and disabled traceback reporting::
|
|||
$ py.test -s -q --tb=no
|
||||
collecting ... collected 4 items
|
||||
FFFF
|
||||
4 failed in 5.62 seconds
|
||||
finalizing <smtplib.SMTP instance at 0x2d60368>
|
||||
finalizing <smtplib.SMTP instance at 0x2d68e60>
|
||||
4 failed in 6.40 seconds
|
||||
finalizing <smtplib.SMTP instance at 0x125d3b0>
|
||||
finalizing <smtplib.SMTP instance at 0x1265b90>
|
||||
|
||||
We see that the two ``smtp`` instances are finalized appropriately.
|
||||
|
||||
|
@ -360,8 +361,8 @@ You can also look at what tests pytest collects without running them::
|
|||
|
||||
$ py.test --collectonly
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8
|
||||
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
|
||||
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
||||
collecting ... collected 4 items
|
||||
<Module 'test_module.py'>
|
||||
<Function 'test_ehlo[merlinux.eu]'>
|
||||
|
@ -369,7 +370,7 @@ You can also look at what tests pytest collects without running them::
|
|||
<Function 'test_ehlo[mail.python.org]'>
|
||||
<Function 'test_noop[mail.python.org]'>
|
||||
|
||||
============================= in 0.02 seconds =============================
|
||||
============================= in 0.01 seconds =============================
|
||||
|
||||
Note that pytest orders your test run by resource usage, minimizing
|
||||
the number of active resources at any given time.
|
||||
|
@ -404,15 +405,15 @@ the ``smtp`` resource by listing it as an input parameter. Let's run this::
|
|||
|
||||
$ py.test -v test_appsetup.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 -- /home/hpk/venv/1/bin/python
|
||||
cachedir: /home/hpk/tmp/doc-exec-414/.cache
|
||||
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
|
||||
cachedir: /home/hpk/tmp/doc-exec-423/.cache
|
||||
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_appsetup.py:12: test_exists[merlinux.eu] PASSED
|
||||
test_appsetup.py:12: test_exists[mail.python.org] PASSED
|
||||
|
||||
========================= 2 passed in 5.37 seconds =========================
|
||||
========================= 2 passed in 6.82 seconds =========================
|
||||
|
||||
Due to the parametrization of ``smtp`` the test will run twice with two
|
||||
different ``App`` instances and respective smtp servers. There is no
|
||||
|
@ -445,17 +446,17 @@ to show the flow of calls::
|
|||
import pytest
|
||||
|
||||
@pytest.factory(scope="module", params=["mod1", "mod2"])
|
||||
def modarg(testcontext):
|
||||
param = testcontext.param
|
||||
def modarg(request):
|
||||
param = request.param
|
||||
print "create", param
|
||||
def fin():
|
||||
print "fin", param
|
||||
testcontext.addfinalizer(fin)
|
||||
request.addfinalizer(fin)
|
||||
return param
|
||||
|
||||
@pytest.factory(scope="function", params=[1,2])
|
||||
def otherarg(testcontext):
|
||||
return testcontext.param
|
||||
def otherarg(request):
|
||||
return request.param
|
||||
|
||||
def test_0(otherarg):
|
||||
print " test0", otherarg
|
||||
|
@ -468,9 +469,9 @@ Let's run the tests in verbose mode and with looking at the print-output::
|
|||
|
||||
$ py.test -v -s test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 -- /home/hpk/venv/1/bin/python
|
||||
cachedir: /home/hpk/tmp/doc-exec-414/.cache
|
||||
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
|
||||
cachedir: /home/hpk/tmp/doc-exec-423/.cache
|
||||
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
||||
collecting ... collected 8 items
|
||||
|
||||
test_module.py:16: test_0[1] PASSED
|
||||
|
@ -501,26 +502,29 @@ a re-ordering of test execution. The finalizer for the ``mod1`` parametrized
|
|||
resource was executed before the ``mod2`` resource was setup.
|
||||
|
||||
.. currentmodule:: _pytest.python
|
||||
.. _`testcontext`:
|
||||
.. _`request`:
|
||||
|
||||
``testcontext``: interacting with test context
|
||||
---------------------------------------------------
|
||||
``request``: interacting with test invocation context
|
||||
-------------------------------------------------------
|
||||
|
||||
The ``testcontext`` object may be used by `@pytest.factory`_ or
|
||||
:ref:`@pytest.setup <setup>` marked functions. It contains
|
||||
information relating to the test context within which the marked
|
||||
function executes. Moreover, you can call
|
||||
``testcontext.addfinalizer(myfinalizer)`` in order to trigger a call to
|
||||
``myfinalizer`` after the last test in the test context has executed.
|
||||
If passed to a parametrized factory ``testcontext.param`` will contain a
|
||||
parameter (one value out of the ``params`` list specified with the
|
||||
`@pytest.factory`_ marker).
|
||||
The ``request`` object may be received by `@pytest.factory`_ or
|
||||
:ref:`@pytest.setup <setup>` marked functions and provides methods
|
||||
|
||||
.. autoclass:: _pytest.python.TestContext()
|
||||
* to inspect attributes of the requesting test context, such as
|
||||
``function``, ``cls``, ``module``, ``session`` and the pytest
|
||||
``config`` object. A request object passed to a parametrized factory
|
||||
will also carry a ``request.param`` object (A parametrized factory and
|
||||
all of its dependent tests will be called with each of the factory-specified
|
||||
``params``).
|
||||
|
||||
* to add finalizers/teardowns to be invoked when the last
|
||||
test of the requesting test context executes
|
||||
|
||||
|
||||
.. autoclass:: _pytest.python.FuncargRequest()
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
.. _`test generators`:
|
||||
.. _`parametrizing-tests`:
|
||||
.. _`parametrized test functions`:
|
||||
|
@ -566,7 +570,6 @@ two ``(input, output)`` arguments of ``test_eval`` function so the latter
|
|||
will be run three times::
|
||||
|
||||
$ py.test -q
|
||||
|
||||
collecting ... collected 13 items
|
||||
....F........
|
||||
================================= FAILURES =================================
|
||||
|
@ -585,7 +588,7 @@ will be run three times::
|
|||
E + where 54 = eval('6*9')
|
||||
|
||||
test_expectation.py:8: AssertionError
|
||||
1 failed, 12 passed in 5.76 seconds
|
||||
1 failed, 12 passed in 6.41 seconds
|
||||
|
||||
As expected only one pair of input/output values fails the simple test function.
|
||||
As usual you can see the ``input`` and ``output`` values in the traceback.
|
||||
|
@ -662,11 +665,11 @@ This means that we only run two tests if no option is passed::
|
|||
$ py.test -q test_compute.py
|
||||
collecting ... collected 2 items
|
||||
..
|
||||
2 passed in 0.02 seconds
|
||||
2 passed in 0.01 seconds
|
||||
|
||||
And we run five tests if we add the ``--all`` option::
|
||||
|
||||
$ py.test -q --all
|
||||
$ py.test -q --all test_compute.py
|
||||
collecting ... collected 5 items
|
||||
....F
|
||||
================================= FAILURES =================================
|
||||
|
@ -679,7 +682,7 @@ And we run five tests if we add the ``--all`` option::
|
|||
E assert 4 < 4
|
||||
|
||||
test_compute.py:3: AssertionError
|
||||
1 failed, 4 passed in 0.03 seconds
|
||||
1 failed, 4 passed in 0.02 seconds
|
||||
|
||||
As expected when running the full range of ``param1`` values
|
||||
we'll get an error on the last one.
|
||||
|
@ -748,13 +751,4 @@ the mechanism was extended and refined:
|
|||
use remains fully supported and existing code using it should run
|
||||
unmodified.
|
||||
|
||||
.. _request:
|
||||
|
||||
The request object passed to old-style factories
|
||||
-----------------------------------------------------------------
|
||||
|
||||
Old-style funcarg factory definitions can receive a :py:class:`~_pytest.python.FuncargRequest` object which
|
||||
provides methods to manage caching and finalization in the context of the
|
||||
test invocation as well as several attributes of the the underlying test item.
|
||||
|
||||
|
||||
|
|
|
@ -331,9 +331,6 @@ test execution:
|
|||
Reference of objects involved in hooks
|
||||
===========================================================
|
||||
|
||||
.. autoclass:: _pytest.python.FuncargRequest()
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.config.Config()
|
||||
:members:
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ you can do with the old-style and much more. Specifically setup functions:
|
|||
- fully interoperate with parametrized resources,
|
||||
- can be defined in a plugin or :ref:`conftest.py <conftest.py>` file and get called
|
||||
on a per-session, per-module, per-class or per-function basis,
|
||||
- can access the :ref:`testcontext <testcontext>` for which the setup is called,
|
||||
- can access the :ref:`request <request>` for which the setup is called,
|
||||
- can precisely control teardown by registering one or multiple
|
||||
teardown functions as soon as they have performed some actions
|
||||
which need undoing, eliminating the no need for a separate
|
||||
|
@ -140,8 +140,8 @@ a per-module setup function. We use a :ref:`resource factory
|
|||
return GlobalResource()
|
||||
|
||||
@pytest.setup(scope="module")
|
||||
def setresource(testcontext, globresource):
|
||||
testcontext.module.globresource = globresource
|
||||
def setresource(request, globresource):
|
||||
request.module.globresource = globresource
|
||||
|
||||
Now any test module can access ``globresource`` as a module global::
|
||||
|
||||
|
@ -178,16 +178,16 @@ factory and also add a finalizer::
|
|||
self.param = param
|
||||
|
||||
@pytest.factory(scope="session", params=[1,2])
|
||||
def globresource(testcontext):
|
||||
g = GlobalResource(testcontext.param)
|
||||
def globresource(request):
|
||||
g = GlobalResource(request.param)
|
||||
def fin():
|
||||
print "finalizing", g
|
||||
testcontext.addfinalizer(fin)
|
||||
request.addfinalizer(fin)
|
||||
return g
|
||||
|
||||
@pytest.setup(scope="module")
|
||||
def setresource(testcontext, globresource):
|
||||
testcontext.module.globresource = globresource
|
||||
def setresource(request, globresource):
|
||||
request.module.globresource = globresource
|
||||
|
||||
And then re-run our test module::
|
||||
|
||||
|
|
|
@ -1590,8 +1590,8 @@ class TestRequestAPI:
|
|||
result = testdir.makeconftest("""
|
||||
import pytest
|
||||
@pytest.setup()
|
||||
def mysetup(testcontext):
|
||||
testcontext.uses_funcarg("db")
|
||||
def mysetup(request):
|
||||
request.uses_funcarg("db")
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
|
@ -1647,9 +1647,9 @@ class TestFuncargFactory:
|
|||
import pytest
|
||||
l = []
|
||||
@pytest.factory(params=[1,2])
|
||||
def arg1(testcontext):
|
||||
def arg1(request):
|
||||
l.append(1)
|
||||
return testcontext.param
|
||||
return request.param
|
||||
|
||||
@pytest.factory()
|
||||
def arg2(arg1):
|
||||
|
@ -1758,7 +1758,7 @@ class TestSetupDiscovery:
|
|||
testdir.makeconftest("""
|
||||
import pytest
|
||||
@pytest.setup()
|
||||
def perfunction(testcontext, tmpdir):
|
||||
def perfunction(request, tmpdir):
|
||||
pass
|
||||
|
||||
@pytest.factory()
|
||||
|
@ -1782,10 +1782,10 @@ class TestSetupDiscovery:
|
|||
setupcalls, allnames = fm.getsetuplist(item.nodeid)
|
||||
assert len(setupcalls) == 2
|
||||
assert setupcalls[0].func.__name__ == "perfunction"
|
||||
assert "testcontext" in setupcalls[0].funcargnames
|
||||
assert "request" in setupcalls[0].funcargnames
|
||||
assert "tmpdir" in setupcalls[0].funcargnames
|
||||
assert setupcalls[1].func.__name__ == "perfunction2"
|
||||
assert "testcontext" not in setupcalls[1].funcargnames
|
||||
assert "request" not in setupcalls[1].funcargnames
|
||||
assert "arg1" in setupcalls[1].funcargnames
|
||||
assert "tmpdir" not in setupcalls[1].funcargnames
|
||||
#assert "tmpdir" in setupcalls[1].depfuncargs
|
||||
|
@ -1842,8 +1842,8 @@ class TestSetupManagement:
|
|||
import pytest
|
||||
l = []
|
||||
@pytest.factory(params=[1,2])
|
||||
def arg(testcontext):
|
||||
return testcontext.param
|
||||
def arg(request):
|
||||
return request.param
|
||||
|
||||
@pytest.setup()
|
||||
def something(arg):
|
||||
|
@ -1868,12 +1868,12 @@ class TestSetupManagement:
|
|||
l = []
|
||||
|
||||
@pytest.factory(scope="session", params=[1,2])
|
||||
def arg(testcontext):
|
||||
return testcontext.param
|
||||
def arg(request):
|
||||
return request.param
|
||||
|
||||
@pytest.setup(scope="function")
|
||||
def append(testcontext, arg):
|
||||
if testcontext.function.__name__ == "test_some":
|
||||
def append(request, arg):
|
||||
if request.function.__name__ == "test_some":
|
||||
l.append(arg)
|
||||
|
||||
def test_some():
|
||||
|
@ -1894,18 +1894,18 @@ class TestSetupManagement:
|
|||
l = []
|
||||
|
||||
@pytest.factory(scope="function", params=[1,2])
|
||||
def farg(testcontext):
|
||||
return testcontext.param
|
||||
def farg(request):
|
||||
return request.param
|
||||
|
||||
@pytest.factory(scope="class", params=list("ab"))
|
||||
def carg(testcontext):
|
||||
return testcontext.param
|
||||
def carg(request):
|
||||
return request.param
|
||||
|
||||
@pytest.setup(scope="class")
|
||||
def append(testcontext, farg, carg):
|
||||
@pytest.setup(scope="function")
|
||||
def append(request, farg, carg):
|
||||
def fin():
|
||||
l.append("fin_%s%s" % (carg, farg))
|
||||
testcontext.addfinalizer(fin)
|
||||
request.addfinalizer(fin)
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
|
@ -1950,8 +1950,8 @@ class TestFuncargMarker:
|
|||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.factory(params=["a", "b", "c"])
|
||||
def arg(testcontext):
|
||||
return testcontext.param
|
||||
def arg(request):
|
||||
return request.param
|
||||
l = []
|
||||
def test_param(arg):
|
||||
l.append(arg)
|
||||
|
@ -2011,10 +2011,10 @@ class TestFuncargMarker:
|
|||
finalized = []
|
||||
created = []
|
||||
@pytest.factory(scope="module")
|
||||
def arg(testcontext):
|
||||
def arg(request):
|
||||
created.append(1)
|
||||
assert testcontext.scope == "module"
|
||||
testcontext.addfinalizer(lambda: finalized.append(1))
|
||||
assert request.scope == "module"
|
||||
request.addfinalizer(lambda: finalized.append(1))
|
||||
def pytest_funcarg__created(request):
|
||||
return len(created)
|
||||
def pytest_funcarg__finalized(request):
|
||||
|
@ -2050,14 +2050,14 @@ class TestFuncargMarker:
|
|||
finalized = []
|
||||
created = []
|
||||
@pytest.factory(scope="function")
|
||||
def arg(testcontext):
|
||||
def arg(request):
|
||||
pass
|
||||
""")
|
||||
testdir.makepyfile(
|
||||
test_mod1="""
|
||||
import pytest
|
||||
@pytest.factory(scope="session")
|
||||
def arg(testcontext):
|
||||
def arg(request):
|
||||
%s
|
||||
def test_1(arg):
|
||||
pass
|
||||
|
@ -2091,8 +2091,8 @@ class TestFuncargMarker:
|
|||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.factory(scope="module", params=["a", "b", "c"])
|
||||
def arg(testcontext):
|
||||
return testcontext.param
|
||||
def arg(request):
|
||||
return request.param
|
||||
l = []
|
||||
def test_param(arg):
|
||||
l.append(arg)
|
||||
|
@ -2109,7 +2109,7 @@ class TestFuncargMarker:
|
|||
testdir.makeconftest("""
|
||||
import pytest
|
||||
@pytest.factory(scope="function")
|
||||
def arg(testcontext):
|
||||
def arg(request):
|
||||
pass
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
|
@ -2131,8 +2131,8 @@ class TestFuncargMarker:
|
|||
import pytest
|
||||
|
||||
@pytest.factory(scope="module", params=[1, 2])
|
||||
def arg(testcontext):
|
||||
return testcontext.param
|
||||
def arg(request):
|
||||
return request.param
|
||||
|
||||
l = []
|
||||
def test_1(arg):
|
||||
|
@ -2198,18 +2198,18 @@ class TestFuncargMarker:
|
|||
l = []
|
||||
|
||||
@pytest.factory(scope="function", params=[1,2])
|
||||
def farg(testcontext):
|
||||
return testcontext.param
|
||||
def farg(request):
|
||||
return request.param
|
||||
|
||||
@pytest.factory(scope="class", params=list("ab"))
|
||||
def carg(testcontext):
|
||||
return testcontext.param
|
||||
def carg(request):
|
||||
return request.param
|
||||
|
||||
@pytest.setup(scope="class")
|
||||
def append(testcontext, farg, carg):
|
||||
@pytest.setup(scope="function")
|
||||
def append(request, farg, carg):
|
||||
def fin():
|
||||
l.append("fin_%s%s" % (carg, farg))
|
||||
testcontext.addfinalizer(fin)
|
||||
request.addfinalizer(fin)
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
|
@ -2244,18 +2244,18 @@ class TestFuncargMarker:
|
|||
import pytest
|
||||
|
||||
@pytest.factory(scope="function", params=[1, 2])
|
||||
def arg(testcontext):
|
||||
param = testcontext.param
|
||||
testcontext.addfinalizer(lambda: l.append("fin:%s" % param))
|
||||
def arg(request):
|
||||
param = request.param
|
||||
request.addfinalizer(lambda: l.append("fin:%s" % param))
|
||||
l.append("create:%s" % param)
|
||||
return testcontext.param
|
||||
return request.param
|
||||
|
||||
@pytest.factory(scope="module", params=["mod1", "mod2"])
|
||||
def modarg(testcontext):
|
||||
param = testcontext.param
|
||||
testcontext.addfinalizer(lambda: l.append("fin:%s" % param))
|
||||
def modarg(request):
|
||||
param = request.param
|
||||
request.addfinalizer(lambda: l.append("fin:%s" % param))
|
||||
l.append("create:%s" % param)
|
||||
return testcontext.param
|
||||
return request.param
|
||||
|
||||
l = []
|
||||
def test_1(arg):
|
||||
|
@ -2288,11 +2288,11 @@ class TestFuncargMarker:
|
|||
import pytest
|
||||
|
||||
@pytest.factory(scope="module", params=[1, 2])
|
||||
def arg(testcontext):
|
||||
testcontext.config.l = l # to access from outer
|
||||
x = testcontext.param
|
||||
testcontext.addfinalizer(lambda: l.append("fin%s" % x))
|
||||
return testcontext.param
|
||||
def arg(request):
|
||||
request.config.l = l # to access from outer
|
||||
x = request.param
|
||||
request.addfinalizer(lambda: l.append("fin%s" % x))
|
||||
return request.param
|
||||
|
||||
l = []
|
||||
def test_1(arg):
|
||||
|
@ -2317,10 +2317,10 @@ class TestFuncargMarker:
|
|||
import pytest
|
||||
|
||||
@pytest.factory(scope="function", params=[1, 2])
|
||||
def arg(testcontext):
|
||||
x = testcontext.param
|
||||
testcontext.addfinalizer(lambda: l.append("fin%s" % x))
|
||||
return testcontext.param
|
||||
def arg(request):
|
||||
x = request.param
|
||||
request.addfinalizer(lambda: l.append("fin%s" % x))
|
||||
return request.param
|
||||
|
||||
l = []
|
||||
def test_1(arg):
|
||||
|
@ -2339,12 +2339,12 @@ class TestFuncargMarker:
|
|||
import pytest
|
||||
|
||||
@pytest.factory(scope="module", params=[1, 2])
|
||||
def arg(testcontext):
|
||||
return testcontext.param
|
||||
def arg(request):
|
||||
return request.param
|
||||
|
||||
@pytest.setup(scope="module")
|
||||
def mysetup(testcontext, arg):
|
||||
testcontext.addfinalizer(lambda: l.append("fin%s" % arg))
|
||||
def mysetup(request, arg):
|
||||
request.addfinalizer(lambda: l.append("fin%s" % arg))
|
||||
l.append("setup%s" % arg)
|
||||
|
||||
l = []
|
||||
|
@ -2376,32 +2376,32 @@ class TestTestContextScopeAccess:
|
|||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.setup(scope=%r)
|
||||
def myscoped(testcontext):
|
||||
def myscoped(request):
|
||||
for x in %r:
|
||||
assert hasattr(testcontext, x)
|
||||
assert hasattr(request, x)
|
||||
for x in %r:
|
||||
pytest.raises(AttributeError, lambda:
|
||||
getattr(testcontext, x))
|
||||
assert testcontext.session
|
||||
assert testcontext.config
|
||||
getattr(request, x))
|
||||
assert request.session
|
||||
assert request.config
|
||||
def test_func():
|
||||
pass
|
||||
""" %(scope, ok.split(), error.split()))
|
||||
reprec = testdir.inline_run()
|
||||
reprec = testdir.inline_run("-l")
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_funcarg(self, testdir, scope, ok, error):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.factory(scope=%r)
|
||||
def arg(testcontext):
|
||||
def arg(request):
|
||||
for x in %r:
|
||||
assert hasattr(testcontext, x)
|
||||
assert hasattr(request, x)
|
||||
for x in %r:
|
||||
pytest.raises(AttributeError, lambda:
|
||||
getattr(testcontext, x))
|
||||
assert testcontext.session
|
||||
assert testcontext.config
|
||||
getattr(request, x))
|
||||
assert request.session
|
||||
assert request.config
|
||||
def test_func(arg):
|
||||
pass
|
||||
""" %(scope, ok.split(), error.split()))
|
||||
|
@ -2414,7 +2414,7 @@ class TestErrors:
|
|||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.factory()
|
||||
def gen(request):
|
||||
def gen(qwe123):
|
||||
return 1
|
||||
def test_something(gen):
|
||||
pass
|
||||
|
@ -2422,8 +2422,8 @@ class TestErrors:
|
|||
result = testdir.runpytest()
|
||||
assert result.ret != 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*def gen(request):*",
|
||||
"*no factory*request*",
|
||||
"*def gen(qwe123):*",
|
||||
"*no factory*qwe123*",
|
||||
"*1 error*",
|
||||
])
|
||||
|
||||
|
@ -2431,7 +2431,7 @@ class TestErrors:
|
|||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.setup()
|
||||
def gen(request):
|
||||
def gen(qwe123):
|
||||
return 1
|
||||
def test_something():
|
||||
pass
|
||||
|
@ -2439,14 +2439,14 @@ class TestErrors:
|
|||
result = testdir.runpytest()
|
||||
assert result.ret != 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*def gen(request):*",
|
||||
"*no factory*request*",
|
||||
"*def gen(qwe123):*",
|
||||
"*no factory*qwe123*",
|
||||
"*1 error*",
|
||||
])
|
||||
|
||||
|
||||
class TestTestContextVarious:
|
||||
def test_newstyle_no_request(self, testdir):
|
||||
def test_newstyle_with_request(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.factory()
|
||||
|
@ -2455,21 +2455,19 @@ class TestTestContextVarious:
|
|||
def test_1(arg):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*no factory found*request*",
|
||||
])
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_setupcontext_no_param(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.factory(params=[1,2])
|
||||
def arg(testcontext):
|
||||
return testcontext.param
|
||||
def arg(request):
|
||||
return request.param
|
||||
|
||||
@pytest.setup()
|
||||
def mysetup(testcontext, arg):
|
||||
assert not hasattr(testcontext, "param")
|
||||
def mysetup(request, arg):
|
||||
assert not hasattr(request, "param")
|
||||
def test_1(arg):
|
||||
assert arg in (1,2)
|
||||
""")
|
||||
|
@ -2505,3 +2503,16 @@ def test_setupdecorator_and_xunit(testdir):
|
|||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=3)
|
||||
|
||||
def test_request_can_be_overridden(testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.factory()
|
||||
def request(request):
|
||||
request.a = 1
|
||||
return request
|
||||
def test_request(request):
|
||||
assert request.a == 1
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
|
Loading…
Reference in New Issue