refine naming, API and docs for py.test.mark mechanism - now contained in pytest_mark plugin
--HG-- branch : trunk
This commit is contained in:
parent
861f34fe90
commit
6c2b1c4363
|
@ -10,5 +10,5 @@ Generator = py.test.collect.Generator
|
||||||
Function = py.test.collect.Function
|
Function = py.test.collect.Function
|
||||||
Instance = py.test.collect.Instance
|
Instance = py.test.collect.Instance
|
||||||
|
|
||||||
pytest_plugins = "default runner capture terminal keyword skipping tmpdir monkeypatch recwarn pdb pastebin unittest helpconfig nose assertion".split()
|
pytest_plugins = "default runner capture terminal mark skipping tmpdir monkeypatch recwarn pdb pastebin unittest helpconfig nose assertion".split()
|
||||||
|
|
||||||
|
|
|
@ -1,60 +1,70 @@
|
||||||
"""
|
"""
|
||||||
mark test functions with keywords that may hold values.
|
generic mechanism for marking python functions.
|
||||||
|
|
||||||
Marking functions by a decorator
|
By using the ``py.test.mark`` helper you can instantiate
|
||||||
|
decorators that will set named meta data on test functions.
|
||||||
|
|
||||||
|
Marking a single function
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
|
|
||||||
By default, all filename parts and class/function names of a test
|
You can "mark" a test function with meta data like this::
|
||||||
function are put into the set of keywords for a given test. You can
|
|
||||||
specify additional kewords like this::
|
|
||||||
|
|
||||||
@py.test.mark.webtest
|
@py.test.mark.webtest
|
||||||
def test_send_http():
|
def test_send_http():
|
||||||
...
|
...
|
||||||
|
|
||||||
This will set an attribute 'webtest' to True on the given test function.
|
This will set a "Marker" instance as a function attribute named "webtest".
|
||||||
You can read the value 'webtest' from the functions __dict__ later.
|
You can also specify parametrized meta data like this::
|
||||||
|
|
||||||
You can also set values for an attribute which are put on an empty
|
|
||||||
dummy object::
|
|
||||||
|
|
||||||
@py.test.mark.webtest(firefox=30)
|
@py.test.mark.webtest(firefox=30)
|
||||||
def test_receive():
|
def test_receive():
|
||||||
...
|
...
|
||||||
|
|
||||||
after which ``test_receive.webtest.firefox == 30`` holds true.
|
The named marker can be accessed like this later::
|
||||||
|
|
||||||
In addition to keyword arguments you can also use positional arguments::
|
test_receive.webtest.kwargs['firefox'] == 30
|
||||||
|
|
||||||
|
In addition to set key-value pairs you can also use positional arguments::
|
||||||
|
|
||||||
@py.test.mark.webtest("triangular")
|
@py.test.mark.webtest("triangular")
|
||||||
def test_receive():
|
def test_receive():
|
||||||
...
|
...
|
||||||
|
|
||||||
after which ``test_receive.webtest._args[0] == 'triangular`` holds true.
|
and later access it with ``test_receive.webtest.args[0] == 'triangular``.
|
||||||
|
|
||||||
|
|
||||||
.. _`scoped-marking`:
|
.. _`scoped-marking`:
|
||||||
|
|
||||||
Marking classes or modules
|
Marking classes or modules
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
|
|
||||||
To mark all methods of a class you can set a class-level attribute::
|
To mark all methods of a class set a ``pytestmark`` attribute like this::
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
class TestClass:
|
class TestClass:
|
||||||
pytestmark = py.test.mark.webtest
|
pytestmark = py.test.mark.webtest
|
||||||
|
|
||||||
the marker function will be applied to all test methods.
|
You can re-use the same markers that you would use for decorating
|
||||||
|
a function - in fact this marker decorator will be applied
|
||||||
|
to all test methods of the class.
|
||||||
|
|
||||||
If you set a marker it inside a test module like this::
|
You can also set a module level marker::
|
||||||
|
|
||||||
|
import py
|
||||||
pytestmark = py.test.mark.webtest
|
pytestmark = py.test.mark.webtest
|
||||||
|
|
||||||
the marker will be applied to all functions and methods of
|
in which case then the marker decorator will be applied to all functions and
|
||||||
that module. The module marker is applied last.
|
methods defined in the module.
|
||||||
|
|
||||||
Outer ``pytestmark`` keywords will overwrite inner keyword
|
The order in which marker functions are called is this::
|
||||||
values. Positional arguments are all appeneded to the
|
|
||||||
same '_args' list.
|
per-function (upon import of module already)
|
||||||
|
per-class
|
||||||
|
per-module
|
||||||
|
|
||||||
|
Later called markers may overwrite previous key-value settings.
|
||||||
|
Positional arguments are all appended to the same 'args' list
|
||||||
|
of the Marker object.
|
||||||
"""
|
"""
|
||||||
import py
|
import py
|
||||||
|
|
||||||
|
@ -86,27 +96,32 @@ class MarkerDecorator:
|
||||||
func = args[0]
|
func = args[0]
|
||||||
holder = getattr(func, self.markname, None)
|
holder = getattr(func, self.markname, None)
|
||||||
if holder is None:
|
if holder is None:
|
||||||
holder = MarkHolder(self.markname, self.args, self.kwargs)
|
holder = Marker(self.markname, self.args, self.kwargs)
|
||||||
setattr(func, self.markname, holder)
|
setattr(func, self.markname, holder)
|
||||||
else:
|
else:
|
||||||
holder.__dict__.update(self.kwargs)
|
holder.kwargs.update(self.kwargs)
|
||||||
holder._args.extend(self.args)
|
holder.args.extend(self.args)
|
||||||
return func
|
return func
|
||||||
else:
|
else:
|
||||||
self.args.extend(args)
|
self.args.extend(args)
|
||||||
self.kwargs.update(kwargs)
|
self.kwargs.update(kwargs)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
class MarkHolder:
|
class Marker:
|
||||||
def __init__(self, name, args, kwargs):
|
def __init__(self, name, args, kwargs):
|
||||||
self._name = name
|
self._name = name
|
||||||
self._args = args
|
self.args = args
|
||||||
self._kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
self.__dict__.update(kwargs)
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name[0] != '_' and name in self.kwargs:
|
||||||
|
py.log._apiwarn("1.1", "use .kwargs attribute to access key-values")
|
||||||
|
return self.kwargs[name]
|
||||||
|
raise AttributeError(name)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Marker %r args=%r kwargs=%r>" % (
|
return "<Marker %r args=%r kwargs=%r>" % (
|
||||||
self._name, self._args, self._kwargs)
|
self._name, self.args, self.kwargs)
|
||||||
|
|
||||||
|
|
||||||
def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
|
def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
|
|
@ -1,16 +1,22 @@
|
||||||
"""
|
"""
|
||||||
advanced conditional skipping for python test functions, classes or modules.
|
advanced skipping for python test functions, classes or modules.
|
||||||
|
|
||||||
You can mark functions, classes or modules for for conditional
|
With this plugin you can mark test functions for conditional skipping
|
||||||
skipping (skipif) or as expected-to-fail (xfail). The difference
|
or as "xfail", expected-to-fail. Skipping a test will avoid running it
|
||||||
between the two is that 'xfail' will still execute test functions
|
while xfail-marked tests will run and result in an inverted outcome:
|
||||||
but it will invert the outcome: a passing test becomes a failure and
|
a pass becomes a failure and a fail becomes a semi-passing one.
|
||||||
a failing test is a semi-passing one. All skip conditions are
|
|
||||||
reported at the end of test run through the terminal reporter.
|
The need for skipping a test is usually connected to a condition.
|
||||||
|
If a test fails under all conditions then it's probably better
|
||||||
|
to mark your test as 'xfail'.
|
||||||
|
|
||||||
|
By passing ``--report=xfailed,skipped`` to the terminal reporter
|
||||||
|
you will see summary information on skips and xfail-run tests
|
||||||
|
at the end of a test run.
|
||||||
|
|
||||||
.. _skipif:
|
.. _skipif:
|
||||||
|
|
||||||
skip a test function conditionally
|
mark a test function to be skipped
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
Here is an example for skipping a test function when
|
Here is an example for skipping a test function when
|
||||||
|
@ -20,6 +26,7 @@ running on Python3::
|
||||||
def test_function():
|
def test_function():
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
During test function setup the skipif condition is
|
During test function setup the skipif condition is
|
||||||
evaluated by calling ``eval(expr, namespace)``. The namespace
|
evaluated by calling ``eval(expr, namespace)``. The namespace
|
||||||
contains the ``sys`` and ``os`` modules as well as the
|
contains the ``sys`` and ``os`` modules as well as the
|
||||||
|
@ -30,76 +37,75 @@ on a test configuration value e.g. like this::
|
||||||
def test_function(...):
|
def test_function(...):
|
||||||
...
|
...
|
||||||
|
|
||||||
Note that `test marking can be declared at whole class- or module level`_.
|
|
||||||
|
|
||||||
.. _`test marking can also be declared at whole class- or module level`: keyword.html#scoped-marking
|
mark many test functions at once
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
As with all metadata function marking you can do it at
|
||||||
|
`whole class- or module level`_. Here is an example
|
||||||
|
for skipping all methods of a test class based on platform::
|
||||||
|
|
||||||
|
class TestPosixCalls:
|
||||||
|
pytestmark = py.test.mark.skipif("sys.platform == 'win32'")
|
||||||
|
|
||||||
|
def test_function(self):
|
||||||
|
# will not be setup or run under 'win32' platform
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
conditionally mark a function as "expected to fail"
|
.. _`whole class- or module level`: mark.html#scoped-marking
|
||||||
|
|
||||||
|
|
||||||
|
mark a test function as expected to fail
|
||||||
-------------------------------------------------------
|
-------------------------------------------------------
|
||||||
|
|
||||||
You can use the ``xfail`` keyword to mark your test functions as
|
You can use the ``xfail`` marker to indicate that you
|
||||||
'expected to fail'::
|
expect the test to fail::
|
||||||
|
|
||||||
@py.test.mark.xfail
|
@py.test.mark.xfail
|
||||||
def test_hello():
|
|
||||||
...
|
|
||||||
|
|
||||||
This test will be executed but no traceback will be reported
|
|
||||||
when it fails. Instead terminal reporting will list it in the
|
|
||||||
"expected to fail" or "unexpectedly passing" sections.
|
|
||||||
As with skipif_ you may selectively expect a failure
|
|
||||||
depending on platform::
|
|
||||||
|
|
||||||
@py.test.mark.xfail("sys.version_info >= (3,0)")
|
|
||||||
def test_function():
|
def test_function():
|
||||||
...
|
...
|
||||||
|
|
||||||
skip/xfail a whole test class or module
|
This test will be run but no traceback will be reported
|
||||||
-------------------------------------------
|
when it fails. Instead terminal reporting will list it in the
|
||||||
|
"expected to fail" or "unexpectedly passing" sections.
|
||||||
|
|
||||||
Instead of marking single functions you can skip
|
Same as with skipif_ you can also selectively expect a failure
|
||||||
a whole class of tests when running on a specific
|
depending on platform::
|
||||||
platform::
|
|
||||||
|
|
||||||
class TestSomething:
|
@py.test.mark.xfail(if"sys.version_info >= (3,0)")
|
||||||
skipif = "sys.platform == 'win32'"
|
|
||||||
|
|
||||||
Or you can mark all test functions as expected
|
def test_function():
|
||||||
to fail for a specific test configuration::
|
...
|
||||||
|
|
||||||
xfail = "config.getvalue('db') == 'mysql'"
|
|
||||||
|
|
||||||
|
|
||||||
skip if a dependency cannot be imported
|
skipping on a missing import dependency
|
||||||
---------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
||||||
You can use a helper to skip on a failing import::
|
You can use the following import helper at module level
|
||||||
|
or within a test or setup function.
|
||||||
|
|
||||||
docutils = py.test.importorskip("docutils")
|
docutils = py.test.importorskip("docutils")
|
||||||
|
|
||||||
You can use this helper at module level or within
|
If ``docutils`` cannot be imported here, this will lead to a
|
||||||
a test or setup function.
|
skip outcome of the test. You can also skip dependeing if
|
||||||
|
if a library does not come with a high enough version::
|
||||||
You can also skip if a library does not come with a high enough version::
|
|
||||||
|
|
||||||
docutils = py.test.importorskip("docutils", minversion="0.3")
|
docutils = py.test.importorskip("docutils", minversion="0.3")
|
||||||
|
|
||||||
The version will be read from the specified module's ``__version__`` attribute.
|
The version will be read from the specified module's ``__version__`` attribute.
|
||||||
|
|
||||||
dynamically skip from within a test or setup
|
imperative skip from within a test or setup function
|
||||||
-------------------------------------------------
|
------------------------------------------------------
|
||||||
|
|
||||||
If you want to skip the execution of a test you can call
|
If for some reason you cannot declare skip-conditions
|
||||||
``py.test.skip()`` within a test, a setup or from a
|
you can also imperatively produce a Skip-outcome from
|
||||||
`funcarg factory`_ function. Example::
|
within test or setup code. Example::
|
||||||
|
|
||||||
def test_function():
|
def test_function():
|
||||||
if not valid_config():
|
if not valid_config():
|
||||||
py.test.skip("unsuppored configuration")
|
py.test.skip("unsuppored configuration")
|
||||||
|
|
||||||
.. _`funcarg factory`: ../funcargs.html#factory
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# XXX py.test.skip, .importorskip and the Skipped class
|
# XXX py.test.skip, .importorskip and the Skipped class
|
||||||
# should also be defined in this plugin, requires thought/changes
|
# should also be defined in this plugin, requires thought/changes
|
||||||
|
@ -177,7 +183,7 @@ def evalexpression(item, keyword):
|
||||||
if markholder:
|
if markholder:
|
||||||
d = {'os': py.std.os, 'sys': py.std.sys, 'config': item.config}
|
d = {'os': py.std.os, 'sys': py.std.sys, 'config': item.config}
|
||||||
expr, result = None, True
|
expr, result = None, True
|
||||||
for expr in markholder._args:
|
for expr in markholder.args:
|
||||||
if isinstance(expr, str):
|
if isinstance(expr, str):
|
||||||
result = eval(expr, d)
|
result = eval(expr, d)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -13,7 +13,7 @@ plugins = [
|
||||||
('plugins for generic reporting and failure logging',
|
('plugins for generic reporting and failure logging',
|
||||||
'pastebin resultlog terminal',),
|
'pastebin resultlog terminal',),
|
||||||
('misc plugins / core functionality',
|
('misc plugins / core functionality',
|
||||||
'helpconfig pdb keyword hooklog')
|
'helpconfig pdb mark hooklog')
|
||||||
#('internal plugins / core functionality',
|
#('internal plugins / core functionality',
|
||||||
# #'pdb keyword hooklog runner execnetcleanup # pytester',
|
# #'pdb keyword hooklog runner execnetcleanup # pytester',
|
||||||
# 'pdb keyword hooklog runner execnetcleanup' # pytester',
|
# 'pdb keyword hooklog runner execnetcleanup' # pytester',
|
||||||
|
|
|
@ -52,7 +52,7 @@ def pytest_generate_tests(metafunc):
|
||||||
multi = getattr(metafunc.function, 'multi', None)
|
multi = getattr(metafunc.function, 'multi', None)
|
||||||
if multi is None:
|
if multi is None:
|
||||||
return
|
return
|
||||||
assert len(multi._kwargs) == 1
|
assert len(multi.kwargs) == 1
|
||||||
for name, l in multi._kwargs.items():
|
for name, l in multi.kwargs.items():
|
||||||
for val in l:
|
for val in l:
|
||||||
metafunc.addcall(funcargs={name: val})
|
metafunc.addcall(funcargs={name: val})
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
Changes between 1.0.2 and '1.1.0b1'
|
Changes between 1.0.2 and '1.1.0b1'
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
* introduce generalized py.test.mark function marking
|
||||||
|
|
||||||
* reshuffle / refine command line grouping
|
* reshuffle / refine command line grouping
|
||||||
|
|
||||||
* deprecate parser.addgroup in favour of getgroup which creates option group
|
* deprecate parser.addgroup in favour of getgroup which creates option group
|
||||||
|
|
|
@ -200,7 +200,7 @@ kewords like this::
|
||||||
and then use those keywords to select tests. See the `pytest_keyword`_
|
and then use those keywords to select tests. See the `pytest_keyword`_
|
||||||
plugin for more information.
|
plugin for more information.
|
||||||
|
|
||||||
.. _`pytest_keyword`: plugin/keyword.html
|
.. _`pytest_keyword`: plugin/mark.html
|
||||||
|
|
||||||
easy to extend
|
easy to extend
|
||||||
=========================================
|
=========================================
|
||||||
|
|
|
@ -113,10 +113,10 @@ command line options
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
``-s``
|
|
||||||
shortcut for --capture=no.
|
|
||||||
``--capture=method``
|
``--capture=method``
|
||||||
set capturing method during tests: fd (default)|sys|no.
|
set capturing method during tests: fd (default)|sys|no.
|
||||||
|
``-s``
|
||||||
|
shortcut for --capture=no.
|
||||||
|
|
||||||
Start improving this plugin in 30 seconds
|
Start improving this plugin in 30 seconds
|
||||||
=========================================
|
=========================================
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
plugins for Python test functions
|
plugins for Python test functions
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
skipping_ advanced conditional skipping for python test functions, classes or modules.
|
skipping_ advanced skipping for python test functions, classes or modules.
|
||||||
|
|
||||||
figleaf_ write and report coverage data with 'figleaf'.
|
figleaf_ write and report coverage data with 'figleaf'.
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ helpconfig_ provide version info, conftest/environment config names.
|
||||||
|
|
||||||
pdb_ interactive debugging with the Python Debugger.
|
pdb_ interactive debugging with the Python Debugger.
|
||||||
|
|
||||||
keyword_ mark test functions with keywords that may hold values.
|
mark_ generic mechanism for marking python functions.
|
||||||
|
|
||||||
hooklog_ log invocations of extension hooks to a file.
|
hooklog_ log invocations of extension hooks to a file.
|
||||||
|
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
|
|
||||||
pytest_keyword plugin
|
|
||||||
=====================
|
|
||||||
|
|
||||||
mark test functions with keywords that may hold values.
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:local:
|
|
||||||
|
|
||||||
Marking functions and setting rich attributes
|
|
||||||
----------------------------------------------------
|
|
||||||
|
|
||||||
By default, all filename parts and class/function names of a test
|
|
||||||
function are put into the set of keywords for a given test. You can
|
|
||||||
specify additional kewords like this::
|
|
||||||
|
|
||||||
@py.test.mark.webtest
|
|
||||||
def test_send_http():
|
|
||||||
...
|
|
||||||
|
|
||||||
This will set an attribute 'webtest' to True on the given test function.
|
|
||||||
You can read the value 'webtest' from the functions __dict__ later.
|
|
||||||
|
|
||||||
You can also set values for an attribute which are put on an empty
|
|
||||||
dummy object::
|
|
||||||
|
|
||||||
@py.test.mark.webtest(firefox=30)
|
|
||||||
def test_receive():
|
|
||||||
...
|
|
||||||
|
|
||||||
after which ``test_receive.webtest.firefox == 30`` holds true.
|
|
||||||
|
|
||||||
In addition to keyword arguments you can also use positional arguments::
|
|
||||||
|
|
||||||
@py.test.mark.webtest("triangular")
|
|
||||||
def test_receive():
|
|
||||||
...
|
|
||||||
|
|
||||||
after which ``test_receive.webtest._1 == 'triangular`` hold true.
|
|
||||||
|
|
||||||
Start improving this plugin in 30 seconds
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
|
|
||||||
1. Download `pytest_keyword.py`_ plugin source code
|
|
||||||
2. put it somewhere as ``pytest_keyword.py`` into your import path
|
|
||||||
3. a subsequent ``py.test`` run will use your local version
|
|
||||||
|
|
||||||
Checkout customize_, other plugins_ or `get in contact`_.
|
|
||||||
|
|
||||||
.. include:: links.txt
|
|
|
@ -3,7 +3,6 @@
|
||||||
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_recwarn.py
|
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_recwarn.py
|
||||||
.. _`unittest`: unittest.html
|
.. _`unittest`: unittest.html
|
||||||
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_monkeypatch.py
|
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_monkeypatch.py
|
||||||
.. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_keyword.py
|
|
||||||
.. _`pastebin`: pastebin.html
|
.. _`pastebin`: pastebin.html
|
||||||
.. _`skipping`: skipping.html
|
.. _`skipping`: skipping.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
|
@ -13,6 +12,7 @@
|
||||||
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_restdoc.py
|
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_restdoc.py
|
||||||
.. _`restdoc`: restdoc.html
|
.. _`restdoc`: restdoc.html
|
||||||
.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_pastebin.py
|
.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_pastebin.py
|
||||||
|
.. _`mark`: mark.html
|
||||||
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_figleaf.py
|
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_figleaf.py
|
||||||
.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_hooklog.py
|
.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_hooklog.py
|
||||||
.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_skipping.py
|
.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_skipping.py
|
||||||
|
@ -20,6 +20,7 @@
|
||||||
.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_helpconfig.py
|
.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_helpconfig.py
|
||||||
.. _`oejskit`: oejskit.html
|
.. _`oejskit`: oejskit.html
|
||||||
.. _`doctest`: doctest.html
|
.. _`doctest`: doctest.html
|
||||||
|
.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_mark.py
|
||||||
.. _`get in contact`: ../../contact.html
|
.. _`get in contact`: ../../contact.html
|
||||||
.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_capture.py
|
.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_capture.py
|
||||||
.. _`figleaf`: figleaf.html
|
.. _`figleaf`: figleaf.html
|
||||||
|
@ -30,7 +31,6 @@
|
||||||
.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_pdb.py
|
.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_pdb.py
|
||||||
.. _`monkeypatch`: monkeypatch.html
|
.. _`monkeypatch`: monkeypatch.html
|
||||||
.. _`resultlog`: resultlog.html
|
.. _`resultlog`: resultlog.html
|
||||||
.. _`keyword`: keyword.html
|
|
||||||
.. _`django`: django.html
|
.. _`django`: django.html
|
||||||
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_unittest.py
|
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_unittest.py
|
||||||
.. _`nose`: nose.html
|
.. _`nose`: nose.html
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
|
||||||
|
pytest_mark plugin
|
||||||
|
==================
|
||||||
|
|
||||||
|
generic mechanism for marking python functions.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
By using the ``py.test.mark`` helper you can instantiate
|
||||||
|
decorators that will set named meta data on test functions.
|
||||||
|
|
||||||
|
Marking a single function
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
|
You can "mark" a test function with meta data like this::
|
||||||
|
|
||||||
|
@py.test.mark.webtest
|
||||||
|
def test_send_http():
|
||||||
|
...
|
||||||
|
|
||||||
|
This will set a "Marker" instance as a function attribute named "webtest".
|
||||||
|
You can also specify parametrized meta data like this::
|
||||||
|
|
||||||
|
@py.test.mark.webtest(firefox=30)
|
||||||
|
def test_receive():
|
||||||
|
...
|
||||||
|
|
||||||
|
The named marker can be accessed like this later::
|
||||||
|
|
||||||
|
test_receive.webtest.kwargs['firefox'] == 30
|
||||||
|
|
||||||
|
In addition to set key-value pairs you can also use positional arguments::
|
||||||
|
|
||||||
|
@py.test.mark.webtest("triangular")
|
||||||
|
def test_receive():
|
||||||
|
...
|
||||||
|
|
||||||
|
and later access it with ``test_receive.webtest.args[0] == 'triangular``.
|
||||||
|
|
||||||
|
.. _`scoped-marking`:
|
||||||
|
|
||||||
|
Marking classes or modules
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
|
To mark all methods of a class set a ``pytestmark`` attribute like this::
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
|
class TestClass:
|
||||||
|
pytestmark = py.test.mark.webtest
|
||||||
|
|
||||||
|
You can re-use the same markers that you would use for decorating
|
||||||
|
a function - in fact this marker decorator will be applied
|
||||||
|
to all test methods of the class.
|
||||||
|
|
||||||
|
You can also set a module level marker::
|
||||||
|
|
||||||
|
import py
|
||||||
|
pytestmark = py.test.mark.webtest
|
||||||
|
|
||||||
|
in which case then the marker decorator will be applied to all functions and
|
||||||
|
methods defined in the module.
|
||||||
|
|
||||||
|
The order in which marker functions are called is this::
|
||||||
|
|
||||||
|
per-function (upon import of module already)
|
||||||
|
per-class
|
||||||
|
per-module
|
||||||
|
|
||||||
|
Later called markers may overwrite previous key-value settings.
|
||||||
|
Positional arguments are all appended to the same 'args' list
|
||||||
|
of the Marker object.
|
||||||
|
|
||||||
|
Start improving this plugin in 30 seconds
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
|
1. Download `pytest_mark.py`_ plugin source code
|
||||||
|
2. put it somewhere as ``pytest_mark.py`` into your import path
|
||||||
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
|
Checkout customize_, other plugins_ or `get in contact`_.
|
||||||
|
|
||||||
|
.. include:: links.txt
|
|
@ -2,32 +2,39 @@
|
||||||
pytest_skipping plugin
|
pytest_skipping plugin
|
||||||
======================
|
======================
|
||||||
|
|
||||||
advanced conditional skipping for python test functions, classes or modules.
|
advanced skipping for python test functions, classes or modules.
|
||||||
|
|
||||||
.. contents::
|
.. contents::
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
You can mark functions, classes or modules for for conditional
|
With this plugin you can mark test functions for conditional skipping
|
||||||
skipping (skipif) or as expected-to-fail (xfail). The difference
|
or as "xfail", expected-to-fail. Skipping a test will avoid running it
|
||||||
between the two is that 'xfail' will still execute test functions
|
while xfail-marked tests will run and result in an inverted outcome:
|
||||||
but it will invert the outcome: a passing test becomes a failure and
|
a pass becomes a failure and a fail becomes a semi-passing one.
|
||||||
a failing test is a semi-passing one. All skip conditions are
|
|
||||||
reported at the end of test run through the terminal reporter.
|
The need for skipping a test is usually connected to a condition.
|
||||||
|
If a test fails under all conditions then it's probably better
|
||||||
|
to mark your test as 'xfail'.
|
||||||
|
|
||||||
|
By passing ``--report=xfailed,skipped`` to the terminal reporter
|
||||||
|
you will see summary information on skips and xfail-run tests
|
||||||
|
at the end of a test run.
|
||||||
|
|
||||||
.. _skipif:
|
.. _skipif:
|
||||||
|
|
||||||
skip a test function conditionally
|
mark a test function to be skipped
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
Here is an example for skipping a test function on Python3::
|
Here is an example for skipping a test function when
|
||||||
|
running on Python3::
|
||||||
|
|
||||||
@py.test.mark.skipif("sys.version_info >= (3,0)")
|
@py.test.mark.skipif("sys.version_info >= (3,0)")
|
||||||
def test_function():
|
def test_function():
|
||||||
...
|
...
|
||||||
|
|
||||||
The 'skipif' marker accepts an **arbitrary python expression**
|
|
||||||
as a condition. When setting up the test function the condition
|
During test function setup the skipif condition is
|
||||||
is evaluated by calling ``eval(expr, namespace)``. The namespace
|
evaluated by calling ``eval(expr, namespace)``. The namespace
|
||||||
contains the ``sys`` and ``os`` modules as well as the
|
contains the ``sys`` and ``os`` modules as well as the
|
||||||
test ``config`` object. The latter allows you to skip based
|
test ``config`` object. The latter allows you to skip based
|
||||||
on a test configuration value e.g. like this::
|
on a test configuration value e.g. like this::
|
||||||
|
@ -37,71 +44,74 @@ on a test configuration value e.g. like this::
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
conditionally mark a function as "expected to fail"
|
mark many test functions at once
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
As with all metadata function marking you can do it at
|
||||||
|
`whole class- or module level`_. Here is an example
|
||||||
|
for skipping all methods of a test class based on platform::
|
||||||
|
|
||||||
|
class TestPosixCalls:
|
||||||
|
pytestmark = py.test.mark.skipif("sys.platform == 'win32'")
|
||||||
|
|
||||||
|
def test_function(self):
|
||||||
|
# will not be setup or run under 'win32' platform
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
.. _`whole class- or module level`: mark.html#scoped-marking
|
||||||
|
|
||||||
|
|
||||||
|
mark a test function as expected to fail
|
||||||
-------------------------------------------------------
|
-------------------------------------------------------
|
||||||
|
|
||||||
You can use the ``xfail`` keyword to mark your test functions as
|
You can use the ``xfail`` marker to indicate that you
|
||||||
'expected to fail'::
|
expect the test to fail::
|
||||||
|
|
||||||
@py.test.mark.xfail
|
@py.test.mark.xfail
|
||||||
def test_hello():
|
|
||||||
...
|
|
||||||
|
|
||||||
This test will be executed but no traceback will be reported
|
|
||||||
when it fails. Instead terminal reporting will list it in the
|
|
||||||
"expected to fail" or "unexpectedly passing" sections.
|
|
||||||
As with skipif_ you may selectively expect a failure
|
|
||||||
depending on platform::
|
|
||||||
|
|
||||||
@py.test.mark.xfail("sys.version_info >= (3,0)")
|
|
||||||
def test_function():
|
def test_function():
|
||||||
...
|
...
|
||||||
|
|
||||||
skip/xfail a whole test class or module
|
This test will be run but no traceback will be reported
|
||||||
-------------------------------------------
|
when it fails. Instead terminal reporting will list it in the
|
||||||
|
"expected to fail" or "unexpectedly passing" sections.
|
||||||
|
|
||||||
Instead of marking single functions you can skip
|
Same as with skipif_ you can also selectively expect a failure
|
||||||
a whole class of tests when running on a specific
|
depending on platform::
|
||||||
platform::
|
|
||||||
|
|
||||||
class TestSomething:
|
@py.test.mark.xfail(if"sys.version_info >= (3,0)")
|
||||||
skipif = "sys.platform == 'win32'"
|
|
||||||
|
|
||||||
Or you can mark all test functions as expected
|
def test_function():
|
||||||
to fail for a specific test configuration::
|
...
|
||||||
|
|
||||||
xfail = "config.getvalue('db') == 'mysql'"
|
|
||||||
|
|
||||||
|
|
||||||
skip if a dependency cannot be imported
|
skipping on a missing import dependency
|
||||||
---------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
||||||
You can use a helper to skip on a failing import::
|
You can use the following import helper at module level
|
||||||
|
or within a test or setup function.
|
||||||
|
|
||||||
docutils = py.test.importorskip("docutils")
|
docutils = py.test.importorskip("docutils")
|
||||||
|
|
||||||
You can use this helper at module level or within
|
If ``docutils`` cannot be imported here, this will lead to a
|
||||||
a test or setup function.
|
skip outcome of the test. You can also skip dependeing if
|
||||||
|
if a library does not come with a high enough version::
|
||||||
You can also skip if a library does not come with a high enough version::
|
|
||||||
|
|
||||||
docutils = py.test.importorskip("docutils", minversion="0.3")
|
docutils = py.test.importorskip("docutils", minversion="0.3")
|
||||||
|
|
||||||
The version will be read from the specified module's ``__version__`` attribute.
|
The version will be read from the specified module's ``__version__`` attribute.
|
||||||
|
|
||||||
dynamically skip from within a test or setup
|
imperative skip from within a test or setup function
|
||||||
-------------------------------------------------
|
------------------------------------------------------
|
||||||
|
|
||||||
If you want to skip the execution of a test you can call
|
If for some reason you cannot declare skip-conditions
|
||||||
``py.test.skip()`` within a test, a setup or from a
|
you can also imperatively produce a Skip-outcome from
|
||||||
`funcarg factory`_ function. Example::
|
within test or setup code. Example::
|
||||||
|
|
||||||
def test_function():
|
def test_function():
|
||||||
if not valid_config():
|
if not valid_config():
|
||||||
py.test.skip("unsuppored configuration")
|
py.test.skip("unsuppored configuration")
|
||||||
|
|
||||||
.. _`funcarg factory`: ../funcargs.html#factory
|
|
||||||
|
|
||||||
Start improving this plugin in 30 seconds
|
Start improving this plugin in 30 seconds
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,24 @@ command line options
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
|
``-v, --verbose``
|
||||||
|
increase verbosity.
|
||||||
|
``-l, --showlocals``
|
||||||
|
show locals in tracebacks (disabled by default).
|
||||||
|
``--report=opts``
|
||||||
|
comma separated reporting options
|
||||||
|
``--tb=style``
|
||||||
|
traceback verboseness (long/short/no).
|
||||||
|
``--fulltrace``
|
||||||
|
don't cut any tracebacks (default is to cut).
|
||||||
``--collectonly``
|
``--collectonly``
|
||||||
only collect tests, don't execute them.
|
only collect tests, don't execute them.
|
||||||
``--traceconfig``
|
``--traceconfig``
|
||||||
trace considerations of conftest.py files.
|
trace considerations of conftest.py files.
|
||||||
``--nomagic``
|
``--nomagic``
|
||||||
don't reinterpret asserts, no traceback cutting.
|
don't reinterpret asserts, no traceback cutting.
|
||||||
``--fulltrace``
|
|
||||||
don't cut any tracebacks (default is to cut).
|
|
||||||
``--debug``
|
``--debug``
|
||||||
generate and show debugging information.
|
generate and show internal debugging information.
|
||||||
|
|
||||||
Start improving this plugin in 30 seconds
|
Start improving this plugin in 30 seconds
|
||||||
=========================================
|
=========================================
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import py
|
import py
|
||||||
from _py.test.plugin.pytest_keyword import Mark
|
from _py.test.plugin.pytest_mark import Mark
|
||||||
|
|
||||||
class TestMark:
|
class TestMark:
|
||||||
def test_pytest_mark_notcallable(self):
|
def test_pytest_mark_notcallable(self):
|
||||||
|
@ -31,15 +31,22 @@ class TestMark:
|
||||||
assert f.world.y == 4
|
assert f.world.y == 4
|
||||||
mark.world(y=1)(f)
|
mark.world(y=1)(f)
|
||||||
assert f.world.y == 1
|
assert f.world.y == 1
|
||||||
assert len(f.world._args) == 0
|
assert len(f.world.args) == 0
|
||||||
|
|
||||||
def test_pytest_mark_positional(self):
|
def test_pytest_mark_positional(self):
|
||||||
mark = Mark()
|
mark = Mark()
|
||||||
def f(): pass
|
def f(): pass
|
||||||
mark.world("hello")(f)
|
mark.world("hello")(f)
|
||||||
assert f.world._args[0] == "hello"
|
assert f.world.args[0] == "hello"
|
||||||
mark.world("world")(f)
|
mark.world("world")(f)
|
||||||
|
|
||||||
|
def test_oldstyle_marker_access(self, recwarn):
|
||||||
|
mark = Mark()
|
||||||
|
def f(): pass
|
||||||
|
mark.world(x=1)(f)
|
||||||
|
assert f.world.x == 1
|
||||||
|
assert recwarn.pop()
|
||||||
|
|
||||||
class TestFunctional:
|
class TestFunctional:
|
||||||
def test_mark_per_function(self, testdir):
|
def test_mark_per_function(self, testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
|
@ -89,10 +96,8 @@ class TestFunctional:
|
||||||
item, = items
|
item, = items
|
||||||
keywords = item.readkeywords()
|
keywords = item.readkeywords()
|
||||||
marker = keywords['hello']
|
marker = keywords['hello']
|
||||||
assert marker._args == ["pos0", "pos1"]
|
assert marker.args == ["pos0", "pos1"]
|
||||||
assert marker.x == 3
|
assert marker.kwargs == {'x': 3, 'y': 2, 'z': 4}
|
||||||
assert marker.y == 2
|
|
||||||
assert marker.z == 4
|
|
||||||
|
|
||||||
def test_mark_other(self, testdir):
|
def test_mark_other(self, testdir):
|
||||||
item = testdir.getitem("""
|
item = testdir.getitem("""
|
Loading…
Reference in New Issue