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:
holger krekel 2012-09-17 16:36:10 +02:00
parent f6b555f5ad
commit 646c2c6001
10 changed files with 326 additions and 369 deletions

View File

@ -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

View File

@ -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. """

View File

@ -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:

View File

@ -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):

View File

@ -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``.

View File

@ -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.

View File

@ -331,9 +331,6 @@ test execution:
Reference of objects involved in hooks
===========================================================
.. autoclass:: _pytest.python.FuncargRequest()
:members:
.. autoclass:: _pytest.config.Config()
:members:

View File

@ -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::

View File

@ -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)

View File

@ -62,6 +62,9 @@ commands=
[testenv:py32]
deps=py>=1.4.0
[testenv:py33]
deps=py>=1.4.0
[testenv:jython]
changedir=testing
commands=