introduce a new -m mark_expression option

This commit is contained in:
holger krekel 2011-11-11 23:02:06 +00:00
parent bc8ee95e72
commit 36c42b5c15
9 changed files with 114 additions and 31 deletions

View File

@ -7,9 +7,10 @@ Changes between 2.1.3 and XXX 2.2.0
allowing to avoid typos and maintain a well described set of markers allowing to avoid typos and maintain a well described set of markers
for your test suite. See exaples at http://pytest.org/latest/mark.html for your test suite. See exaples at http://pytest.org/latest/mark.html
and its links. and its links.
- XXX introduce "-m marker" option to select tests based on markers - introduce "-m marker" option to select tests based on markers
(this is a stricter more predictable version of '-k' which also matches (this is a stricter and more predictable version of '-k' in that
substrings and compares against the test function name etc.) "-m" only matches complete markers and has more obvious rules
for and/or semantics.
- new feature to help optimizing the speed of your tests: - new feature to help optimizing the speed of your tests:
--durations=N option for displaying N slowest test calls --durations=N option for displaying N slowest test calls
and setup/teardown methods. and setup/teardown methods.

View File

@ -1,2 +1,2 @@
# #
__version__ = '2.2.0.dev6' __version__ = '2.2.0.dev7'

View File

@ -14,6 +14,12 @@ def pytest_addoption(parser):
"Terminate expression with ':' to make the first match match " "Terminate expression with ':' to make the first match match "
"all subsequent tests (usually file-order). ") "all subsequent tests (usually file-order). ")
group._addoption("-m",
action="store", dest="markexpr", default="", metavar="MARKEXPR",
help="only run tests which match given mark expression. "
"An expression is a python expression which can use "
"marker names.")
group.addoption("--markers", action="store_true", help= group.addoption("--markers", action="store_true", help=
"show markers (builtin, plugin and per-project ones).") "show markers (builtin, plugin and per-project ones).")
@ -34,10 +40,11 @@ pytest_cmdline_main.tryfirst = True
def pytest_collection_modifyitems(items, config): def pytest_collection_modifyitems(items, config):
keywordexpr = config.option.keyword keywordexpr = config.option.keyword
if not keywordexpr: matchexpr = config.option.markexpr
if not keywordexpr and not matchexpr:
return return
selectuntil = False selectuntil = False
if keywordexpr[-1] == ":": if keywordexpr[-1:] == ":":
selectuntil = True selectuntil = True
keywordexpr = keywordexpr[:-1] keywordexpr = keywordexpr[:-1]
@ -47,14 +54,27 @@ def pytest_collection_modifyitems(items, config):
if keywordexpr and skipbykeyword(colitem, keywordexpr): if keywordexpr and skipbykeyword(colitem, keywordexpr):
deselected.append(colitem) deselected.append(colitem)
else: else:
remaining.append(colitem)
if selectuntil: if selectuntil:
keywordexpr = None keywordexpr = None
if matchexpr:
if not matchmark(colitem, matchexpr):
deselected.append(colitem)
continue
remaining.append(colitem)
if deselected: if deselected:
config.hook.pytest_deselected(items=deselected) config.hook.pytest_deselected(items=deselected)
items[:] = remaining items[:] = remaining
class BoolDict:
def __init__(self, mydict):
self._mydict = mydict
def __getitem__(self, name):
return name in self._mydict
def matchmark(colitem, matchexpr):
return eval(matchexpr, {}, BoolDict(colitem.obj.__dict__))
def pytest_configure(config): def pytest_configure(config):
if config.option.strict: if config.option.strict:
pytest.mark._config = config pytest.mark._config = config

View File

@ -440,8 +440,15 @@ class TerminalReporter:
def summary_deselected(self): def summary_deselected(self):
if 'deselected' in self.stats: if 'deselected' in self.stats:
l = []
k = self.config.option.keyword
if k:
l.append("-k%s" % k)
m = self.config.option.markexpr
if m:
l.append("-m %r" % m)
self.write_sep("=", "%d tests deselected by %r" %( self.write_sep("=", "%d tests deselected by %r" %(
len(self.stats['deselected']), self.config.option.keyword), bold=True) len(self.stats['deselected']), " ".join(l)), bold=True)
def repr_pythonversion(v=None): def repr_pythonversion(v=None):
if v is None: if v is None:

View File

@ -40,7 +40,7 @@ clean:
-rm -rf $(BUILDDIR)/* -rm -rf $(BUILDDIR)/*
install: html install: html
@rsync -avz _build/html/ pytest.org:/www/pytest.org/latest @rsync -avz _build/html/ pytest.org:/www/pytest.org/2.2.0.dev7
installpdf: latexpdf installpdf: latexpdf
@scp $(BUILDDIR)/latex/pytest.pdf pytest.org:/www/pytest.org/latest @scp $(BUILDDIR)/latex/pytest.pdf pytest.org:/www/pytest.org/latest

View File

@ -26,8 +26,8 @@ Welcome to pytest!
- **supports functional testing and complex test setups** - **supports functional testing and complex test setups**
- (new in 2.2) :ref:`durations` - (new in 2.2) :ref:`durations`
- (much improved in 2.2) :ref:`marking and test selection <mark>`
- advanced :ref:`skip and xfail` - advanced :ref:`skip and xfail`
- generic :ref:`marking and test selection <mark>`
- can :ref:`distribute tests to multiple CPUs <xdistcpu>` through :ref:`xdist plugin <xdist>` - can :ref:`distribute tests to multiple CPUs <xdistcpu>` through :ref:`xdist plugin <xdist>`
- can :ref:`continuously re-run failing tests <looponfailing>` - can :ref:`continuously re-run failing tests <looponfailing>`
- many :ref:`builtin helpers <pytest helpers>` - many :ref:`builtin helpers <pytest helpers>`

View File

@ -28,16 +28,34 @@ You can "mark" a test function with custom metadata like this::
@pytest.mark.webtest @pytest.mark.webtest
def test_send_http(): def test_send_http():
pass # perform some webtest test for your app pass # perform some webtest test for your app
def test_something_quick():
pass
.. versionadded:: 2.2 .. versionadded:: 2.2
You can restrict a test run only tests marked with ``webtest`` like this:: You can then restrict a test run to only run tests marked with ``webtest``::
$ py.test -m webtest $ py.test -v -m webtest
============================= test session starts ==============================
platform darwin -- Python 2.7.1 -- pytest-2.2.0.dev6 -- /Users/hpk/venv/0/bin/python
collecting ... collected 2 items
test_server.py:3: test_send_http PASSED
===================== 1 tests deselected by "-m 'webtest'" =====================
==================== 1 passed, 1 deselected in 0.01 seconds ====================
Or the inverse, running all tests except the webtest ones:: Or the inverse, running all tests except the webtest ones::
$ py.test -m "not webtest" $ py.test -v -m "not webtest"
============================= test session starts ==============================
platform darwin -- Python 2.7.1 -- pytest-2.2.0.dev6 -- /Users/hpk/venv/0/bin/python
collecting ... collected 2 items
test_server.py:6: test_something_quick PASSED
=================== 1 tests deselected by "-m 'not webtest'" ===================
==================== 1 passed, 1 deselected in 0.01 seconds ====================
Registering markers Registering markers
------------------------------------- -------------------------------------
@ -53,9 +71,19 @@ Registering markers for your test suite is simple::
markers = markers =
webtest: mark a test as a webtest. webtest: mark a test as a webtest.
You can ask which markers exist for your test suite:: You can ask which markers exist for your test suite - the list includes our just defined ``webtest`` markers::
$ py.test --markers $ py.test --markers
@pytest.mark.webtest: mark a test as a webtest.
@pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform.
@pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied.
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
For an example on how to add and work markers from a plugin, see For an example on how to add and work markers from a plugin, see
:ref:`adding a custom marker from a plugin`. :ref:`adding a custom marker from a plugin`.
@ -118,39 +146,42 @@ methods defined in the module.
Using ``-k TEXT`` to select tests Using ``-k TEXT`` to select tests
---------------------------------------------------- ----------------------------------------------------
You can use the ``-k`` command line option to select tests:: You can use the ``-k`` command line option to only run tests with names that match the given argument::
$ py.test -k webtest # running with the above defined examples yields $ py.test -k send_http # running with the above defined examples
=========================== test session starts ============================ ============================= test session starts ==============================
platform darwin -- Python 2.7.1 -- pytest-2.1.3 platform darwin -- Python 2.7.1 -- pytest-2.2.0.dev6
collecting ... collected 4 items collecting ... collected 4 items
test_mark.py .. test_server.py .
test_mark_classlevel.py ..
========================= 4 passed in 0.03 seconds ========================= ===================== 3 tests deselected by '-ksend_http' ======================
==================== 1 passed, 3 deselected in 0.02 seconds ====================
And you can also run all tests except the ones that match the keyword:: And you can also run all tests except the ones that match the keyword::
$ py.test -k-webtest $ py.test -k-send_http
=========================== test session starts ============================ ============================= test session starts ==============================
platform darwin -- Python 2.7.1 -- pytest-2.1.3 platform darwin -- Python 2.7.1 -- pytest-2.2.0.dev6
collecting ... collected 4 items collecting ... collected 4 items
===================== 4 tests deselected by '-webtest' ===================== test_mark_classlevel.py ..
======================= 4 deselected in 0.02 seconds ======================= test_server.py .
===================== 1 tests deselected by '-k-send_http' =====================
==================== 3 passed, 1 deselected in 0.03 seconds ====================
Or to only select the class:: Or to only select the class::
$ py.test -kTestClass $ py.test -kTestClass
=========================== test session starts ============================ ============================= test session starts ==============================
platform darwin -- Python 2.7.1 -- pytest-2.1.3 platform darwin -- Python 2.7.1 -- pytest-2.2.0.dev6
collecting ... collected 4 items collecting ... collected 4 items
test_mark_classlevel.py .. test_mark_classlevel.py ..
==================== 2 tests deselected by 'TestClass' ===================== ===================== 2 tests deselected by '-kTestClass' ======================
================== 2 passed, 2 deselected in 0.02 seconds ================== ==================== 2 passed, 2 deselected in 0.02 seconds ====================
API reference for mark related objects API reference for mark related objects
------------------------------------------------ ------------------------------------------------

View File

@ -24,7 +24,7 @@ def main():
name='pytest', name='pytest',
description='py.test: simple powerful testing with Python', description='py.test: simple powerful testing with Python',
long_description = long_description, long_description = long_description,
version='2.2.0.dev6', version='2.2.0.dev7',
url='http://pytest.org', url='http://pytest.org',
license='MIT license', license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],

View File

@ -114,6 +114,30 @@ def test_strict_prohibits_unregistered_markers(testdir):
"*unregisteredmark*not*registered*", "*unregisteredmark*not*registered*",
]) ])
@pytest.mark.multi(spec=[
("xyz", ("test_one",)),
("xyz and xyz2", ()),
("xyz2", ("test_two",)),
("xyz or xyz2", ("test_one", "test_two"),)
])
def test_mark_option(spec, testdir):
testdir.makepyfile("""
import pytest
@pytest.mark.xyz
def test_one():
pass
@pytest.mark.xyz2
def test_two():
pass
""")
opt, passed_result = spec
rec = testdir.inline_run("-m", opt)
passed, skipped, fail = rec.listoutcomes()
passed = [x.nodeid.split("::")[-1] for x in passed]
assert len(passed) == len(passed_result)
assert list(passed) == list(passed_result)
class TestFunctional: class TestFunctional:
def test_mark_per_function(self, testdir): def test_mark_per_function(self, testdir):