* refined funcarg docs and CHANGELOG
* fixed funcarg setup and error-raising issue --HG-- branch : 1.0.x
This commit is contained in:
parent
cd5ffcc605
commit
183af95526
12
CHANGELOG
12
CHANGELOG
|
@ -1,7 +1,15 @@
|
||||||
Changes between 1.0.0b7 and 1.0.0
|
Changes between 1.0.0b7 and 1.0.0b8
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
* tweaked doctest output for docstrings in py modules
|
* docs: refined funcargs doc, use the term
|
||||||
|
"factory" instead of "provider", added a new
|
||||||
|
talk/tutorial doc page
|
||||||
|
|
||||||
|
* fixed teardown problem related to partially failing funcarg setups
|
||||||
|
(thanks MrTopf for reporting)
|
||||||
|
|
||||||
|
* tweaked doctest output for docstrings in py modules,
|
||||||
|
thanks Radomir.
|
||||||
|
|
||||||
Changes between 1.0.0b3 and 1.0.0b7
|
Changes between 1.0.0b3 and 1.0.0b7
|
||||||
=============================================
|
=============================================
|
||||||
|
|
|
@ -1,45 +1,34 @@
|
||||||
==========================================================
|
==========================================================
|
||||||
**funcargs**: pythonic test setup and parametrization
|
**funcargs**: test function arguments FTW
|
||||||
==========================================================
|
==========================================================
|
||||||
|
|
||||||
Since version 1.0 py.test introduces test function arguments,
|
Since version 1.0 py.test features the "funcarg" mechanism which
|
||||||
in short "funcargs" for your Python test functions. The basic idea
|
allows a test function to take arguments which will be independently
|
||||||
that your unit-, functional- or acceptance test functions can name
|
provided by factory functions. Factory functions are automatically
|
||||||
arguments and py.test will discover a matching provider from your
|
discovered and allow to encapsulate all neccessary setup and glue code
|
||||||
test configuration. The mechanism complements the automatic
|
for running tests. Compared to `xUnit style`_ the new mechanism is
|
||||||
discovery of test files, classes and functions which follows
|
meant to:
|
||||||
the `Convention over Configuration`_ strategy. By discovering and
|
|
||||||
calling functions ("funcarg providers") that provide values for your
|
|
||||||
actual test functions it becomes easy to:
|
|
||||||
|
|
||||||
* separate test function code from test state setup/fixtures
|
* make test functions easier to write and to read
|
||||||
* manage test value setup and teardown depending on
|
* isolate test fixture creation to a single place
|
||||||
command line options or configuration
|
* bring new flexibility and power to test state management
|
||||||
* parametrize multiple runs of the same test functions
|
* enable running of a test function with different values
|
||||||
* present useful debug info if setting up test state goes wrong
|
(superseding `old-style generative tests`_)
|
||||||
|
* to enable creation of helper objects that interact with the execution
|
||||||
|
of a test function, see the `blog post about the monkeypatch funcarg`_.
|
||||||
|
|
||||||
Using funcargs, test functions become more expressive,
|
If you find issues or have further suggestions for improving
|
||||||
more "templaty" and more test-aspect oriented. In fact,
|
the mechanism you are welcome to checkout `contact possibilities`_ page.
|
||||||
funcarg mechanisms are meant to be complete and
|
|
||||||
convenient enough to
|
|
||||||
|
|
||||||
* substitute and improve on most usages of `xUnit style`_ setup.
|
|
||||||
For a simple example of how funcargs compare
|
|
||||||
to xUnit setup, see the `blog post about
|
|
||||||
the monkeypatch funcarg`_.
|
|
||||||
|
|
||||||
* substitute and improve on all usages of `old-style generative tests`_,
|
|
||||||
i.e. test functions that use the "yield" statement.
|
|
||||||
Using yield in test functions is deprecated since 1.0.
|
|
||||||
|
|
||||||
|
.. _`contact possibilities`: ../contact.html
|
||||||
|
|
||||||
.. _`blog post about the monkeypatch funcarg`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
.. _`blog post about the monkeypatch funcarg`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
||||||
.. _`xUnit style`: xunit_setup.html
|
.. _`xUnit style`: xunit_setup.html
|
||||||
.. _`old-style generative tests`: features.html#generative-tests
|
.. _`old-style generative tests`: features.html#generative-tests
|
||||||
|
|
||||||
.. _`funcarg provider`:
|
.. _`funcarg factory`:
|
||||||
|
|
||||||
funcarg providers: setting up test function arguments
|
funcarg factories: setting up test function arguments
|
||||||
==============================================================
|
==============================================================
|
||||||
|
|
||||||
Test functions can specify one ore more arguments ("funcargs")
|
Test functions can specify one ore more arguments ("funcargs")
|
||||||
|
@ -49,22 +38,22 @@ example that you can put into a test module:
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
# ./test_simpleprovider.py
|
# ./test_simplefactory.py
|
||||||
def pytest_funcarg__myfuncarg(request):
|
def pytest_funcarg__myfuncarg(request):
|
||||||
return 42
|
return 42
|
||||||
|
|
||||||
def test_function(myfuncarg):
|
def test_function(myfuncarg):
|
||||||
assert myfuncarg == 17
|
assert myfuncarg == 17
|
||||||
|
|
||||||
If you run this with ``py.test test_simpleprovider.py`` you see something like this:
|
If you run this with ``py.test test_simplefactory.py`` you see something like this:
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
============================ test session starts ============================
|
============================ test session starts ============================
|
||||||
python: platform linux2 -- Python 2.6.2
|
python: platform linux2 -- Python 2.6.2
|
||||||
test object 1: /home/hpk/hg/py/trunk/example/funcarg/test_simpleprovider.py
|
test object 1: /home/hpk/hg/py/trunk/example/funcarg/test_simplefactory.py
|
||||||
|
|
||||||
test_simpleprovider.py F
|
test_simplefactory.py F
|
||||||
|
|
||||||
================================= FAILURES ==================================
|
================================= FAILURES ==================================
|
||||||
_______________________________ test_function _______________________________
|
_______________________________ test_function _______________________________
|
||||||
|
@ -75,7 +64,7 @@ If you run this with ``py.test test_simpleprovider.py`` you see something like t
|
||||||
> assert myfuncarg == 17
|
> assert myfuncarg == 17
|
||||||
E assert 42 == 17
|
E assert 42 == 17
|
||||||
|
|
||||||
test_simpleprovider.py:6: AssertionError
|
test_simplefactory.py:6: AssertionError
|
||||||
========================= 1 failed in 0.11 seconds ==========================
|
========================= 1 failed in 0.11 seconds ==========================
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,7 +73,7 @@ Here is how py.test comes to execute this test function:
|
||||||
|
|
||||||
1. py.test discovers the ``test_function`` because of the ``test_`` prefix.
|
1. py.test discovers the ``test_function`` because of the ``test_`` prefix.
|
||||||
The test function needs a function argument named ``myfuncarg``.
|
The test function needs a function argument named ``myfuncarg``.
|
||||||
A matching provider function is discovered by looking for the special
|
A matching factory function is discovered by looking for the special
|
||||||
name ``pytest_funcarg__myfuncarg``.
|
name ``pytest_funcarg__myfuncarg``.
|
||||||
|
|
||||||
2. ``pytest_funcarg__myfuncarg(request)`` is called and
|
2. ``pytest_funcarg__myfuncarg(request)`` is called and
|
||||||
|
@ -96,18 +85,17 @@ Note that if you misspell a function argument or want
|
||||||
to use one that isn't available, an error with a list of
|
to use one that isn't available, an error with a list of
|
||||||
available function argument is provided.
|
available function argument is provided.
|
||||||
|
|
||||||
For more interesting provider functions that make good use of the
|
For more interesting factory functions that make good use of the
|
||||||
`request object`_ please see the `application setup tutorial example`_.
|
`request object`_ please see the `application setup tutorial example`_.
|
||||||
|
|
||||||
.. _`request object`:
|
.. _`request object`:
|
||||||
|
|
||||||
funcarg request objects
|
funcarg factory request objects
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
Request objects are passed to funcarg providers. They
|
Request objects are passed to funcarg factories and allow
|
||||||
encapsulate a request for a function argument for a
|
to access test configuration, test context and `useful caching
|
||||||
specific test function. Request objects allow providers
|
and finalization helpers`_. Here is a list of attributes:
|
||||||
to access test configuration and test context:
|
|
||||||
|
|
||||||
``request.function``: python function object requesting the argument
|
``request.function``: python function object requesting the argument
|
||||||
|
|
||||||
|
@ -119,9 +107,11 @@ to access test configuration and test context:
|
||||||
|
|
||||||
``request.param``: if exists was passed by a `parametrizing test generator`_
|
``request.param``: if exists was passed by a `parametrizing test generator`_
|
||||||
|
|
||||||
|
.. _`useful caching and finalization helpers`:
|
||||||
|
|
||||||
teardown/cleanup after test function execution
|
|
||||||
------------------------------------------------
|
registering funcarg related finalizers/cleanup
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
@ -130,7 +120,8 @@ teardown/cleanup after test function execution
|
||||||
|
|
||||||
Calling ``request.addfinalizer()`` is useful for scheduling teardown
|
Calling ``request.addfinalizer()`` is useful for scheduling teardown
|
||||||
functions. Here is an example for providing a ``myfile``
|
functions. Here is an example for providing a ``myfile``
|
||||||
object that is to be closed when the test function finishes.
|
object that is to be closed when the execution of a
|
||||||
|
test function finishes.
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
@ -140,8 +131,8 @@ object that is to be closed when the test function finishes.
|
||||||
return myfile
|
return myfile
|
||||||
|
|
||||||
|
|
||||||
perform scope-specific setup and cleanup
|
managing fixtures across test modules and test runs
|
||||||
---------------------------------------------
|
----------------------------------------------------------
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
@ -156,13 +147,16 @@ perform scope-specific setup and cleanup
|
||||||
scope == 'session': when tests of the session have run.
|
scope == 'session': when tests of the session have run.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
example for providing a value that is to be setup only once during a test session:
|
Calling ``request.cached_setup()`` helps you to manage fixture
|
||||||
|
objects across several scopes. For example, for creating a Database object
|
||||||
|
that is to be setup only once during a test session you can use the helper
|
||||||
|
like this:
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
def pytest_funcarg__db(request):
|
def pytest_funcarg__database(request):
|
||||||
return request.cached_setup(
|
return request.cached_setup(
|
||||||
setup=lambda: ExpensiveSetup(request.config.option.db),
|
setup=lambda: Database("..."),
|
||||||
teardown=lambda val: val.close(),
|
teardown=lambda val: val.close(),
|
||||||
scope="session"
|
scope="session"
|
||||||
)
|
)
|
||||||
|
@ -171,23 +165,18 @@ example for providing a value that is to be setup only once during a test sessio
|
||||||
requesting values of other funcargs
|
requesting values of other funcargs
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
Inside a funcarg provider, you sometimes may want to use a
|
|
||||||
different function argument which may be specified with
|
|
||||||
the test function or not. For such purposes you can
|
|
||||||
dynamically request a funcarg value:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
def getfuncargvalue(name):
|
def getfuncargvalue(name):
|
||||||
""" Lookup and call function argument provider for the given name.
|
""" Lookup and call function argument factory for the given name.
|
||||||
Each function argument is only requested once per function setup.
|
Each function argument is only created once per function setup.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
You can also use this function if you want to `decorate a funcarg`_
|
``request.getfuncargvalue(name)`` calls another funcarg factory function.
|
||||||
locally, i.e. you want to provide the normal value but add/do something
|
You can use this function if you want to `decorate a funcarg`_, i.e.
|
||||||
extra. If a provider cannot be found a ``request.Error`` exception will be
|
you want to provide the "normal" value but add something
|
||||||
raised.
|
extra. If a factory cannot be found a ``request.Error``
|
||||||
|
exception will be raised.
|
||||||
|
|
||||||
.. _`test generators`:
|
.. _`test generators`:
|
||||||
.. _`parametrizing test generator`:
|
.. _`parametrizing test generator`:
|
||||||
|
@ -195,7 +184,7 @@ raised.
|
||||||
generating parametrized tests with funcargs
|
generating parametrized tests with funcargs
|
||||||
===========================================================
|
===========================================================
|
||||||
|
|
||||||
You can directly parametrize multiple runs of the same test
|
You can parametrize multiple runs of the same test
|
||||||
function by adding new test function calls with different
|
function by adding new test function calls with different
|
||||||
function argument values. Let's look at a simple self-contained
|
function argument values. Let's look at a simple self-contained
|
||||||
example:
|
example:
|
||||||
|
@ -280,7 +269,7 @@ the stringified counter of the list of added calls will be used.
|
||||||
invocations for a given test function.
|
invocations for a given test function.
|
||||||
|
|
||||||
``param`` if specified will be seen by any
|
``param`` if specified will be seen by any
|
||||||
`funcarg provider`_ as a ``request.param`` attribute.
|
`funcarg factory`_ as a ``request.param`` attribute.
|
||||||
Setting it is called *indirect parametrization*.
|
Setting it is called *indirect parametrization*.
|
||||||
|
|
||||||
Indirect parametrization is preferable if test values are
|
Indirect parametrization is preferable if test values are
|
||||||
|
@ -322,12 +311,12 @@ specific setup.
|
||||||
answer = app.question()
|
answer = app.question()
|
||||||
assert answer == 42
|
assert answer == 42
|
||||||
|
|
||||||
To run this test py.test needs to find and call a provider to
|
To run this test py.test needs to find and call a factory to
|
||||||
obtain the required ``mysetup`` function argument. The test
|
obtain the required ``mysetup`` function argument. The test
|
||||||
function interacts with the provided application specific setup.
|
function interacts with the provided application specific setup.
|
||||||
|
|
||||||
To provide the ``mysetup`` function argument we write down
|
To provide the ``mysetup`` function argument we write down
|
||||||
a provider method in a `local plugin`_ by putting the
|
a factory method in a `local plugin`_ by putting the
|
||||||
following code into a local ``conftest.py``:
|
following code into a local ``conftest.py``:
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
@ -448,7 +437,7 @@ Running ``py.test test_ssh.py`` without specifying a command line option will re
|
||||||
conftest.py:23: [1] Skipped: 'specify ssh host with --ssh'
|
conftest.py:23: [1] Skipped: 'specify ssh host with --ssh'
|
||||||
====================== 1 skipped in 0.11 seconds ======================
|
====================== 1 skipped in 0.11 seconds ======================
|
||||||
|
|
||||||
Note especially how the test function could stay clear knowing about how to construct test state values or when to skip and with what message. The test function can concentrate on actual test code and test state providers can interact with execution of tests.
|
Note especially how the test function could stay clear knowing about how to construct test state values or when to skip and with what message. The test function can concentrate on actual test code and test state factories can interact with execution of tests.
|
||||||
|
|
||||||
If you specify a command line option like ``py.test --ssh=python.org`` the test will get un-skipped and actually execute.
|
If you specify a command line option like ``py.test --ssh=python.org`` the test will get un-skipped and actually execute.
|
||||||
|
|
||||||
|
@ -508,7 +497,7 @@ extend the `accept example`_ by putting this in our test class:
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
def pytest_funcarg__accept(self, request):
|
def pytest_funcarg__accept(self, request):
|
||||||
arg = request.getfuncargvalue("accept") # call the next provider
|
arg = request.getfuncargvalue("accept") # call the next factory
|
||||||
# create a special layout in our tempdir
|
# create a special layout in our tempdir
|
||||||
arg.tmpdir.mkdir("special")
|
arg.tmpdir.mkdir("special")
|
||||||
return arg
|
return arg
|
||||||
|
@ -517,8 +506,8 @@ extend the `accept example`_ by putting this in our test class:
|
||||||
def test_sometest(self, accept):
|
def test_sometest(self, accept):
|
||||||
assert accept.tmpdir.join("special").check()
|
assert accept.tmpdir.join("special").check()
|
||||||
|
|
||||||
Our module level provider will be invoked first and it can
|
Our module level factory will be invoked first and it can
|
||||||
ask its request object to call the next provider and then
|
ask its request object to call the next factory and then
|
||||||
decorate its result. This mechanism allows us to stay
|
decorate its result. This mechanism allows us to stay
|
||||||
ignorant of how/where the function argument is provided -
|
ignorant of how/where the function argument is provided -
|
||||||
in our example from a `conftest plugin`_.
|
in our example from a `conftest plugin`_.
|
||||||
|
@ -543,7 +532,7 @@ When experimenting with funcargs we also
|
||||||
considered an explicit registration mechanism, i.e. calling a register
|
considered an explicit registration mechanism, i.e. calling a register
|
||||||
method on the config object. But lacking a good use case for this
|
method on the config object. But lacking a good use case for this
|
||||||
indirection and flexibility we decided to go for `Convention over
|
indirection and flexibility we decided to go for `Convention over
|
||||||
Configuration`_ and allow to directly specify the provider. It has the
|
Configuration`_ and allow to directly specify the factory. It has the
|
||||||
positive implication that you should be able to "grep" for
|
positive implication that you should be able to "grep" for
|
||||||
``pytest_funcarg__MYARG`` and will find all providing sites (usually
|
``pytest_funcarg__MYARG`` and will find all providing sites (usually
|
||||||
exactly one).
|
exactly one).
|
||||||
|
|
|
@ -165,7 +165,7 @@ class FuncargRequest:
|
||||||
line = "%s:%s" %(fspath, lineno)
|
line = "%s:%s" %(fspath, lineno)
|
||||||
msg = "funcargument %r not found for: %s" %(argname, line)
|
msg = "funcargument %r not found for: %s" %(argname, line)
|
||||||
msg += "\n available funcargs: %s" %(", ".join(available),)
|
msg += "\n available funcargs: %s" %(", ".join(available),)
|
||||||
raise LookupError(msg)
|
raise self.Error(msg)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -254,5 +254,5 @@ class SetupState(object):
|
||||||
break
|
break
|
||||||
self._pop_and_teardown()
|
self._pop_and_teardown()
|
||||||
for col in needed_collectors[len(self.stack):]:
|
for col in needed_collectors[len(self.stack):]:
|
||||||
col.setup()
|
|
||||||
self.stack.append(col)
|
self.stack.append(col)
|
||||||
|
col.setup()
|
||||||
|
|
|
@ -152,6 +152,7 @@ class TestRequest:
|
||||||
def test_func(something): pass
|
def test_func(something): pass
|
||||||
""")
|
""")
|
||||||
req = funcargs.FuncargRequest(item)
|
req = funcargs.FuncargRequest(item)
|
||||||
|
py.test.raises(req.Error, req.getfuncargvalue, "notexists")
|
||||||
val = req.getfuncargvalue("something")
|
val = req.getfuncargvalue("something")
|
||||||
assert val == 1
|
assert val == 1
|
||||||
val = req.getfuncargvalue("something")
|
val = req.getfuncargvalue("something")
|
||||||
|
@ -181,6 +182,21 @@ class TestRequest:
|
||||||
print ss.stack
|
print ss.stack
|
||||||
assert teardownlist == [1]
|
assert teardownlist == [1]
|
||||||
|
|
||||||
|
def test_request_addfinalizer_partial_setup_failure(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
l = []
|
||||||
|
def pytest_funcarg__something(request):
|
||||||
|
request.addfinalizer(lambda: l.append(None))
|
||||||
|
def test_func(something, missingarg):
|
||||||
|
pass
|
||||||
|
def test_second():
|
||||||
|
assert len(l) == 1
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
assert result.stdout.fnmatch_lines([
|
||||||
|
"*1 failed*1 passed*"
|
||||||
|
])
|
||||||
|
|
||||||
def test_request_getmodulepath(self, testdir):
|
def test_request_getmodulepath(self, testdir):
|
||||||
modcol = testdir.getmodulecol("def test_somefunc(): pass")
|
modcol = testdir.getmodulecol("def test_somefunc(): pass")
|
||||||
item, = testdir.genitems([modcol])
|
item, = testdir.genitems([modcol])
|
||||||
|
|
Loading…
Reference in New Issue