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
for your test suite. See exaples at http://pytest.org/latest/mark.html
and its links.
- XXX introduce "-m marker" option to select tests based on markers
(this is a stricter more predictable version of '-k' which also matches
substrings and compares against the test function name etc.)
- introduce "-m marker" option to select tests based on markers
(this is a stricter and more predictable version of '-k' in that
"-m" only matches complete markers and has more obvious rules
for and/or semantics.
- new feature to help optimizing the speed of your tests:
--durations=N option for displaying N slowest test calls
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 "
"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=
"show markers (builtin, plugin and per-project ones).")
@ -34,10 +40,11 @@ pytest_cmdline_main.tryfirst = True
def pytest_collection_modifyitems(items, config):
keywordexpr = config.option.keyword
if not keywordexpr:
matchexpr = config.option.markexpr
if not keywordexpr and not matchexpr:
return
selectuntil = False
if keywordexpr[-1] == ":":
if keywordexpr[-1:] == ":":
selectuntil = True
keywordexpr = keywordexpr[:-1]
@ -47,14 +54,27 @@ def pytest_collection_modifyitems(items, config):
if keywordexpr and skipbykeyword(colitem, keywordexpr):
deselected.append(colitem)
else:
remaining.append(colitem)
if selectuntil:
keywordexpr = None
if matchexpr:
if not matchmark(colitem, matchexpr):
deselected.append(colitem)
continue
remaining.append(colitem)
if deselected:
config.hook.pytest_deselected(items=deselected)
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):
if config.option.strict:
pytest.mark._config = config

View File

@ -440,8 +440,15 @@ class TerminalReporter:
def summary_deselected(self):
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" %(
len(self.stats['deselected']), self.config.option.keyword), bold=True)
len(self.stats['deselected']), " ".join(l)), bold=True)
def repr_pythonversion(v=None):
if v is None:

View File

@ -40,7 +40,7 @@ clean:
-rm -rf $(BUILDDIR)/*
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
@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**
- (new in 2.2) :ref:`durations`
- (much improved in 2.2) :ref:`marking and test selection <mark>`
- 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:`continuously re-run failing tests <looponfailing>`
- 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
def test_send_http():
pass # perform some webtest test for your app
def test_something_quick():
pass
.. 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::
$ 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
-------------------------------------
@ -53,9 +71,19 @@ Registering markers for your test suite is simple::
markers =
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
@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
:ref:`adding a custom marker from a plugin`.
@ -118,39 +146,42 @@ methods defined in the module.
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
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.1.3
$ py.test -k send_http # running with the above defined examples
============================= test session starts ==============================
platform darwin -- Python 2.7.1 -- pytest-2.2.0.dev6
collecting ... collected 4 items
test_mark.py ..
test_mark_classlevel.py ..
test_server.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::
$ py.test -k-webtest
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.1.3
$ py.test -k-send_http
============================= test session starts ==============================
platform darwin -- Python 2.7.1 -- pytest-2.2.0.dev6
collecting ... collected 4 items
===================== 4 tests deselected by '-webtest' =====================
======================= 4 deselected in 0.02 seconds =======================
test_mark_classlevel.py ..
test_server.py .
===================== 1 tests deselected by '-k-send_http' =====================
==================== 3 passed, 1 deselected in 0.03 seconds ====================
Or to only select the class::
$ py.test -kTestClass
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.1.3
============================= test session starts ==============================
platform darwin -- Python 2.7.1 -- pytest-2.2.0.dev6
collecting ... collected 4 items
test_mark_classlevel.py ..
==================== 2 tests deselected by 'TestClass' =====================
================== 2 passed, 2 deselected in 0.02 seconds ==================
===================== 2 tests deselected by '-kTestClass' ======================
==================== 2 passed, 2 deselected in 0.02 seconds ====================
API reference for mark related objects
------------------------------------------------

View File

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

View File

@ -114,6 +114,30 @@ def test_strict_prohibits_unregistered_markers(testdir):
"*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:
def test_mark_per_function(self, testdir):