.. _`mark examples`: Working with custom markers ================================================= Here are some example using the :ref:`mark` mechanism. Marking test functions and selecting them for a run ---------------------------------------------------- You can "mark" a test function with custom metadata like this:: # content of test_server.py import pytest @pytest.mark.webtest def test_send_http(): pass # perform some webtest test for your app def test_something_quick(): pass def test_another(): pass .. versionadded:: 2.2 You can then restrict a test run to only run tests marked with ``webtest``:: $ py.test -v -m webtest =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 3 items test_server.py:3: test_send_http PASSED =================== 2 tests deselected by "-m 'webtest'" =================== ================== 1 passed, 2 deselected in 0.01 seconds ================== Or the inverse, running all tests except the webtest ones:: $ py.test -v -m "not webtest" =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 3 items test_server.py:6: test_something_quick PASSED test_server.py:8: test_another PASSED ================= 1 tests deselected by "-m 'not webtest'" ================= ================== 2 passed, 1 deselected in 0.01 seconds ================== Using ``-k expr`` to select tests based on their name ------------------------------------------------------- .. versionadded: 2.0/2.3.4 You can use the ``-k`` command line option to specify an expression which implements a substring match on the test names instead of the exact match on markers that ``-m`` provides. This makes it easy to select tests based on their names:: $ py.test -v -k http # running with the above defined example module =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 3 items test_server.py:3: test_send_http PASSED ====================== 2 tests deselected by '-khttp' ====================== ================== 1 passed, 2 deselected in 0.01 seconds ================== And you can also run all tests except the ones that match the keyword:: $ py.test -k "not send_http" -v =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 3 items test_server.py:6: test_something_quick PASSED test_server.py:8: test_another PASSED ================= 1 tests deselected by '-knot send_http' ================== ================== 2 passed, 1 deselected in 0.01 seconds ================== Or to select "http" and "quick" tests:: $ py.test -k "http or quick" -v =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 3 items test_server.py:3: test_send_http PASSED test_server.py:6: test_something_quick PASSED ================= 1 tests deselected by '-khttp or quick' ================== ================== 2 passed, 1 deselected in 0.01 seconds ================== Registering markers ------------------------------------- .. versionadded:: 2.2 .. ini-syntax for custom markers: Registering markers for your test suite is simple:: # content of pytest.ini [pytest] markers = webtest: mark a test as a webtest. 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(condition): skip the given test function if eval(condition) results in 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. see http://pytest.org/latest/skipping.html @pytest.mark.xfail(condition, reason=None, run=True): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. See http://pytest.org/latest/skipping.html @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in multiple different argument value sets. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2. see http://pytest.org/latest/parametrize.html for more info and examples. @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures @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 with markers from a plugin, see :ref:`adding a custom marker from a plugin`. .. note:: It is recommended to explicitely register markers so that: * there is one place in your test suite defining your markers * asking for existing markers via ``py.test --markers`` gives good output * typos in function markers are treated as an error if you use the ``--strict`` option. Later versions of py.test are probably going to treat non-registered markers as an error. .. _`scoped-marking`: Marking whole classes or modules ---------------------------------------------------- If you are programming with Python 2.6 or later you may use ``pytest.mark`` decorators with classes to apply markers to all of its test methods:: # content of test_mark_classlevel.py import pytest @pytest.mark.webtest class TestClass: def test_startup(self): pass def test_startup_and_more(self): pass This is equivalent to directly applying the decorator to the two test functions. To remain backward-compatible with Python 2.4 you can also set a ``pytestmark`` attribute on a TestClass like this:: import pytest class TestClass: pytestmark = pytest.mark.webtest or if you need to use multiple markers you can use a list:: import pytest class TestClass: pytestmark = [pytest.mark.webtest, pytest.mark.slowtest] You can also set a module level marker:: import pytest pytestmark = pytest.mark.webtest in which case it will be applied to all functions and methods defined in the module. .. _`adding a custom marker from a plugin`: Custom marker and command line option to control test runs ---------------------------------------------------------- .. regendoc:wipe Plugins can provide custom markers and implement specific behaviour based on it. This is a self-contained example which adds a command line option and a parametrized test function marker to run tests specifies via named environments:: # content of conftest.py import pytest def pytest_addoption(parser): parser.addoption("-E", action="store", metavar="NAME", help="only run tests matching the environment NAME.") def pytest_configure(config): # register an additional marker config.addinivalue_line("markers", "env(name): mark test to run only on named environment") def pytest_runtest_setup(item): envmarker = item.keywords.get("env", None) if envmarker is not None: envname = envmarker.args[0] if envname != item.config.getoption("-E"): pytest.skip("test requires env %r" % envname) A test file using this local plugin:: # content of test_someenv.py import pytest @pytest.mark.env("stage1") def test_basic_db_operation(): pass and an example invocations specifying a different environment than what the test needs:: $ py.test -E stage2 =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 collected 1 items test_someenv.py s ======================== 1 skipped in 0.01 seconds ========================= and here is one that specifies exactly the environment needed:: $ py.test -E stage1 =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 collected 1 items test_someenv.py . ========================= 1 passed in 0.01 seconds ========================= The ``--markers`` option always gives you a list of available markers:: $ py.test --markers @pytest.mark.env(name): mark test to run only on named environment @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in 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. see http://pytest.org/latest/skipping.html @pytest.mark.xfail(condition, reason=None, run=True): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. See http://pytest.org/latest/skipping.html @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in multiple different argument value sets. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2. see http://pytest.org/latest/parametrize.html for more info and examples. @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures @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. Reading markers which were set from multiple places ---------------------------------------------------- .. versionadded: 2.2.2 .. regendoc:wipe If you are heavily using markers in your test suite you may encounter the case where a marker is applied several times to a test function. From plugin code you can read over all such settings. Example:: # content of test_mark_three_times.py import pytest pytestmark = pytest.mark.glob("module", x=1) @pytest.mark.glob("class", x=2) class TestClass: @pytest.mark.glob("function", x=3) def test_something(self): pass Here we have the marker "glob" applied three times to the same test function. From a conftest file we can read it like this:: # content of conftest.py import sys def pytest_runtest_setup(item): g = item.keywords.get("glob", None) if g is not None: for info in g: print ("glob args=%s kwargs=%s" %(info.args, info.kwargs)) sys.stdout.flush() Let's run this without capturing output and see what we get:: $ py.test -q -s glob args=('function',) kwargs={'x': 3} glob args=('class',) kwargs={'x': 2} glob args=('module',) kwargs={'x': 1} . marking platform specific tests with pytest -------------------------------------------------------------- .. regendoc:wipe Consider you have a test suite which marks tests for particular platforms, namely ``pytest.mark.osx``, ``pytest.mark.win32`` etc. and you also have tests that run on all platforms and have no specific marker. If you now want to have a way to only run the tests for your particular platform, you could use the following plugin:: # content of conftest.py # import sys import pytest ALL = set("osx linux2 win32".split()) def pytest_runtest_setup(item): if isinstance(item, item.Function): plat = sys.platform if plat not in item.keywords: if ALL.intersection(item.keywords): pytest.skip("cannot run on platform %s" %(plat)) then tests will be skipped if they were specified for a different platform. Let's do a little test file to show how this looks like:: # content of test_plat.py import pytest @pytest.mark.osx def test_if_apple_is_evil(): pass @pytest.mark.linux2 def test_if_linux_works(): pass @pytest.mark.win32 def test_if_win32_crashes(): pass def test_runs_everywhere(): pass then you will see two test skipped and two executed tests as expected:: $ py.test -rs # this option reports skip reasons =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 collected 4 items test_plat.py s.s. ========================= short test summary info ========================== SKIP [2] /tmp/doc-exec-133/conftest.py:12: cannot run on platform linux2 =================== 2 passed, 2 skipped in 0.01 seconds ==================== Note that if you specify a platform via the marker-command line option like this:: $ py.test -m linux2 =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 collected 4 items test_plat.py . =================== 3 tests deselected by "-m 'linux2'" ==================== ================== 1 passed, 3 deselected in 0.01 seconds ================== then the unmarked-tests will not be run. It is thus a way to restrict the run to the specific tests. Automatically adding markers based on test names -------------------------------------------------------- .. regendoc:wipe If you a test suite where test function names indicate a certain type of test, you can implement a hook that automatically defines markers so that you can use the ``-m`` option with it. Let's look at this test module:: # content of test_module.py def test_interface_simple(): assert 0 def test_interface_complex(): assert 0 def test_event_simple(): assert 0 def test_something_else(): assert 0 We want to dynamically define two markers and can do it in a ``conftest.py`` plugin:: # content of conftest.py import pytest def pytest_collection_modifyitems(items): for item in items: if "interface" in item.nodeid: item.keywords["interface"] = pytest.mark.interface elif "event" in item.nodeid: item.keywords["event"] = pytest.mark.event We can now use the ``-m option`` to select one set:: $ py.test -m interface --tb=short =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 collected 4 items test_module.py FF ================================= FAILURES ================================= __________________________ test_interface_simple ___________________________ test_module.py:3: in test_interface_simple > assert 0 E assert 0 __________________________ test_interface_complex __________________________ test_module.py:6: in test_interface_complex > assert 0 E assert 0 ================== 2 tests deselected by "-m 'interface'" ================== ================== 2 failed, 2 deselected in 0.01 seconds ================== or to select both "event" and "interface" tests:: $ py.test -m "interface or event" --tb=short =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.4 collected 4 items test_module.py FFF ================================= FAILURES ================================= __________________________ test_interface_simple ___________________________ test_module.py:3: in test_interface_simple > assert 0 E assert 0 __________________________ test_interface_complex __________________________ test_module.py:6: in test_interface_complex > assert 0 E assert 0 ____________________________ test_event_simple _____________________________ test_module.py:9: in test_event_simple > assert 0 E assert 0 ============= 1 tests deselected by "-m 'interface or event'" ============== ================== 3 failed, 1 deselected in 0.02 seconds ==================