2012-10-07 19:06:17 +08:00
|
|
|
|
|
|
|
.. _`test generators`:
|
|
|
|
.. _`parametrizing-tests`:
|
|
|
|
.. _`parametrized test functions`:
|
|
|
|
.. _`parametrize`:
|
|
|
|
|
2012-10-18 18:24:50 +08:00
|
|
|
.. _`parametrize-basics`:
|
|
|
|
|
2021-03-11 03:18:21 +08:00
|
|
|
How to parametrize fixtures and test functions
|
2012-10-07 19:06:17 +08:00
|
|
|
==========================================================================
|
|
|
|
|
2017-05-09 22:02:08 +08:00
|
|
|
pytest enables test parametrization at several levels:
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2018-05-18 16:19:46 +08:00
|
|
|
- :py:func:`pytest.fixture` allows one to :ref:`parametrize fixture
|
2017-05-09 22:02:08 +08:00
|
|
|
functions <fixture-parametrize>`.
|
2012-10-08 19:19:31 +08:00
|
|
|
|
2018-05-18 16:19:46 +08:00
|
|
|
* `@pytest.mark.parametrize`_ allows one to define multiple sets of
|
2017-05-09 22:02:08 +08:00
|
|
|
arguments and fixtures at the test function or class.
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2018-05-18 16:19:46 +08:00
|
|
|
* `pytest_generate_tests`_ allows one to define custom parametrization
|
2017-05-09 22:02:08 +08:00
|
|
|
schemes or extensions.
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2012-10-18 18:24:50 +08:00
|
|
|
.. _parametrizemark:
|
2012-10-07 19:06:17 +08:00
|
|
|
.. _`@pytest.mark.parametrize`:
|
|
|
|
|
2012-10-09 20:35:17 +08:00
|
|
|
|
2012-10-07 19:06:17 +08:00
|
|
|
``@pytest.mark.parametrize``: parametrizing test functions
|
|
|
|
---------------------------------------------------------------------
|
|
|
|
|
|
|
|
.. regendoc: wipe
|
|
|
|
|
2019-04-28 23:37:58 +08:00
|
|
|
|
|
|
|
|
2015-07-10 08:50:38 +08:00
|
|
|
Several improvements.
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2018-03-01 07:34:20 +08:00
|
|
|
The builtin :ref:`pytest.mark.parametrize ref` decorator enables
|
2012-10-07 19:06:17 +08:00
|
|
|
parametrization of arguments for a test function. Here is a typical example
|
2012-10-08 19:19:31 +08:00
|
|
|
of a test function that implements checking that a certain input leads
|
2019-04-12 19:50:26 +08:00
|
|
|
to an expected output:
|
|
|
|
|
|
|
|
.. code-block:: python
|
2012-10-07 19:06:17 +08:00
|
|
|
|
|
|
|
# content of test_expectation.py
|
|
|
|
import pytest
|
2019-04-12 19:50:26 +08:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
|
2016-01-13 09:01:34 +08:00
|
|
|
def test_eval(test_input, expected):
|
|
|
|
assert eval(test_input) == expected
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2016-01-13 09:01:34 +08:00
|
|
|
Here, the ``@parametrize`` decorator defines three different ``(test_input,expected)``
|
2014-01-10 05:27:23 +08:00
|
|
|
tuples so that the ``test_eval`` function will run three times using
|
2018-11-24 13:41:22 +08:00
|
|
|
them in turn:
|
|
|
|
|
|
|
|
.. code-block:: pytest
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2016-06-21 22:16:57 +08:00
|
|
|
$ pytest
|
2017-11-23 23:33:41 +08:00
|
|
|
=========================== test session starts ============================
|
2021-08-31 01:40:59 +08:00
|
|
|
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
|
2021-10-04 14:56:26 +08:00
|
|
|
cachedir: .pytest_cache
|
|
|
|
rootdir: /home/sweet/project
|
2012-10-09 20:35:17 +08:00
|
|
|
collected 3 items
|
2018-05-18 16:19:46 +08:00
|
|
|
|
2017-11-23 23:33:41 +08:00
|
|
|
test_expectation.py ..F [100%]
|
2018-05-18 16:19:46 +08:00
|
|
|
|
2017-11-23 23:33:41 +08:00
|
|
|
================================= FAILURES =================================
|
|
|
|
____________________________ test_eval[6*9-42] _____________________________
|
2018-05-18 16:19:46 +08:00
|
|
|
|
2016-01-13 09:01:34 +08:00
|
|
|
test_input = '6*9', expected = 42
|
2018-05-18 16:19:46 +08:00
|
|
|
|
2019-04-15 22:24:17 +08:00
|
|
|
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
|
2016-01-13 09:01:34 +08:00
|
|
|
def test_eval(test_input, expected):
|
|
|
|
> assert eval(test_input) == expected
|
2017-05-13 04:17:40 +08:00
|
|
|
E AssertionError: assert 54 == 42
|
2012-10-07 19:06:17 +08:00
|
|
|
E + where 54 = eval('6*9')
|
2018-05-18 16:19:46 +08:00
|
|
|
|
2019-04-15 22:24:17 +08:00
|
|
|
test_expectation.py:6: AssertionError
|
2020-03-11 22:23:25 +08:00
|
|
|
========================= short test summary info ==========================
|
|
|
|
FAILED test_expectation.py::test_eval[6*9-42] - AssertionError: assert 54...
|
2019-08-30 23:43:47 +08:00
|
|
|
======================= 1 failed, 2 passed in 0.12s ========================
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2020-07-20 20:12:48 +08:00
|
|
|
.. note::
|
|
|
|
|
|
|
|
Parameter values are passed as-is to tests (no copy whatsoever).
|
|
|
|
|
|
|
|
For example, if you pass a list or a dict as a parameter value, and
|
|
|
|
the test case code mutates it, the mutations will be reflected in subsequent
|
|
|
|
test case calls.
|
|
|
|
|
2017-10-12 00:11:50 +08:00
|
|
|
.. note::
|
|
|
|
|
|
|
|
pytest by default escapes any non-ascii characters used in unicode strings
|
|
|
|
for the parametrization because it has several downsides.
|
2020-07-20 20:12:48 +08:00
|
|
|
If however you would like to use unicode strings in parametrization
|
|
|
|
and see them in the terminal as is (non-escaped), use this option
|
|
|
|
in your ``pytest.ini``:
|
2017-10-12 00:11:50 +08:00
|
|
|
|
|
|
|
.. code-block:: ini
|
|
|
|
|
|
|
|
[pytest]
|
|
|
|
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
|
|
|
|
|
2019-03-27 23:05:33 +08:00
|
|
|
Keep in mind however that this might cause unwanted side effects and
|
2020-07-20 20:12:48 +08:00
|
|
|
even bugs depending on the OS used and plugins currently installed,
|
|
|
|
so use it at your own risk.
|
2017-10-12 00:11:50 +08:00
|
|
|
|
|
|
|
|
2013-05-28 16:32:54 +08:00
|
|
|
As designed in this example, only one pair of input/output values fails
|
|
|
|
the simple test function. And as usual with test function arguments,
|
|
|
|
you can see the ``input`` and ``output`` values in the traceback.
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2013-05-28 16:32:54 +08:00
|
|
|
Note that you could also use the parametrize marker on a class or a module
|
2021-07-31 21:26:25 +08:00
|
|
|
(see :ref:`mark`) which would invoke several functions with the argument sets,
|
|
|
|
for instance:
|
|
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
|
|
|
|
class TestClass:
|
|
|
|
def test_simple_case(self, n, expected):
|
|
|
|
assert n + 1 == expected
|
|
|
|
|
|
|
|
def test_weird_simple_case(self, n, expected):
|
|
|
|
assert (n * 1) + 1 == expected
|
|
|
|
|
|
|
|
|
|
|
|
To parametrize all tests in a module, you can assign to the :globalvar:`pytestmark` global variable:
|
|
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
|
|
|
|
|
|
|
|
|
|
|
|
class TestClass:
|
|
|
|
def test_simple_case(self, n, expected):
|
|
|
|
assert n + 1 == expected
|
|
|
|
|
|
|
|
def test_weird_simple_case(self, n, expected):
|
|
|
|
assert (n * 1) + 1 == expected
|
|
|
|
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2013-05-28 16:32:54 +08:00
|
|
|
It is also possible to mark individual test instances within parametrize,
|
2019-04-12 19:50:26 +08:00
|
|
|
for example with the builtin ``mark.xfail``:
|
|
|
|
|
|
|
|
.. code-block:: python
|
2013-05-21 09:12:45 +08:00
|
|
|
|
|
|
|
# content of test_expectation.py
|
|
|
|
import pytest
|
2019-04-12 19:50:26 +08:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"test_input,expected",
|
|
|
|
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
|
|
|
|
)
|
2016-01-13 09:01:34 +08:00
|
|
|
def test_eval(test_input, expected):
|
|
|
|
assert eval(test_input) == expected
|
2013-05-21 09:12:45 +08:00
|
|
|
|
2018-11-24 13:41:22 +08:00
|
|
|
Let's run this:
|
|
|
|
|
|
|
|
.. code-block:: pytest
|
2013-05-28 16:32:54 +08:00
|
|
|
|
2016-06-21 22:16:57 +08:00
|
|
|
$ pytest
|
2017-11-23 23:33:41 +08:00
|
|
|
=========================== test session starts ============================
|
2021-08-31 01:40:59 +08:00
|
|
|
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
|
2021-10-04 14:56:26 +08:00
|
|
|
cachedir: .pytest_cache
|
|
|
|
rootdir: /home/sweet/project
|
2013-05-28 16:32:54 +08:00
|
|
|
collected 3 items
|
2018-05-18 16:19:46 +08:00
|
|
|
|
2017-11-23 23:33:41 +08:00
|
|
|
test_expectation.py ..x [100%]
|
2018-05-18 16:19:46 +08:00
|
|
|
|
2019-08-30 23:43:47 +08:00
|
|
|
======================= 2 passed, 1 xfailed in 0.12s =======================
|
2013-05-28 16:32:54 +08:00
|
|
|
|
|
|
|
The one parameter set which caused a failure previously now
|
2020-06-11 16:22:47 +08:00
|
|
|
shows up as an "xfailed" (expected to fail) test.
|
2013-05-28 16:32:54 +08:00
|
|
|
|
2018-10-24 03:44:30 +08:00
|
|
|
In case the values provided to ``parametrize`` result in an empty list - for
|
|
|
|
example, if they're dynamically generated by some function - the behaviour of
|
|
|
|
pytest is defined by the :confval:`empty_parameter_set_mark` option.
|
|
|
|
|
2015-07-26 20:39:13 +08:00
|
|
|
To get all combinations of multiple parametrized arguments you can stack
|
2019-04-12 19:50:26 +08:00
|
|
|
``parametrize`` decorators:
|
|
|
|
|
|
|
|
.. code-block:: python
|
2015-07-26 20:39:13 +08:00
|
|
|
|
|
|
|
import pytest
|
2019-04-12 19:50:26 +08:00
|
|
|
|
|
|
|
|
2015-07-26 20:39:13 +08:00
|
|
|
@pytest.mark.parametrize("x", [0, 1])
|
|
|
|
@pytest.mark.parametrize("y", [2, 3])
|
|
|
|
def test_foo(x, y):
|
|
|
|
pass
|
|
|
|
|
2018-05-18 16:19:46 +08:00
|
|
|
This will run the test with the arguments set to ``x=0/y=2``, ``x=1/y=2``,
|
2017-12-16 23:31:48 +08:00
|
|
|
``x=0/y=3``, and ``x=1/y=3`` exhausting parameters in the order of the decorators.
|
2017-12-16 22:25:02 +08:00
|
|
|
|
2012-10-07 19:06:17 +08:00
|
|
|
.. _`pytest_generate_tests`:
|
|
|
|
|
|
|
|
Basic ``pytest_generate_tests`` example
|
|
|
|
---------------------------------------------
|
|
|
|
|
2012-10-08 19:19:31 +08:00
|
|
|
Sometimes you may want to implement your own parametrization scheme
|
|
|
|
or implement some dynamism for determining the parameters or scope
|
2014-01-18 19:31:33 +08:00
|
|
|
of a fixture. For this, you can use the ``pytest_generate_tests`` hook
|
2012-10-09 20:35:17 +08:00
|
|
|
which is called when collecting a test function. Through the passed in
|
2016-02-16 06:19:07 +08:00
|
|
|
``metafunc`` object you can inspect the requesting test context and, most
|
2012-10-09 20:35:17 +08:00
|
|
|
importantly, you can call ``metafunc.parametrize()`` to cause
|
2014-01-18 19:31:33 +08:00
|
|
|
parametrization.
|
2012-10-09 20:35:17 +08:00
|
|
|
|
|
|
|
For example, let's say we want to run a test taking string inputs which
|
2014-01-18 19:31:33 +08:00
|
|
|
we want to set via a new ``pytest`` command line option. Let's first write
|
2019-04-12 19:50:26 +08:00
|
|
|
a simple test accepting a ``stringinput`` fixture function argument:
|
|
|
|
|
|
|
|
.. code-block:: python
|
2012-10-08 19:19:31 +08:00
|
|
|
|
|
|
|
# content of test_strings.py
|
|
|
|
|
2019-04-12 19:50:26 +08:00
|
|
|
|
2012-10-08 19:19:31 +08:00
|
|
|
def test_valid_string(stringinput):
|
|
|
|
assert stringinput.isalpha()
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2014-01-18 19:31:33 +08:00
|
|
|
Now we add a ``conftest.py`` file containing the addition of a
|
2019-04-12 19:50:26 +08:00
|
|
|
command line option and the parametrization of our test function:
|
|
|
|
|
|
|
|
.. code-block:: python
|
2012-10-07 19:06:17 +08:00
|
|
|
|
|
|
|
# content of conftest.py
|
|
|
|
|
2019-04-12 19:50:26 +08:00
|
|
|
|
2012-10-07 19:06:17 +08:00
|
|
|
def pytest_addoption(parser):
|
2019-04-12 19:50:26 +08:00
|
|
|
parser.addoption(
|
|
|
|
"--stringinput",
|
|
|
|
action="append",
|
|
|
|
default=[],
|
|
|
|
help="list of stringinputs to pass to test functions",
|
|
|
|
)
|
|
|
|
|
2012-10-07 19:06:17 +08:00
|
|
|
|
|
|
|
def pytest_generate_tests(metafunc):
|
2019-04-12 19:50:26 +08:00
|
|
|
if "stringinput" in metafunc.fixturenames:
|
|
|
|
metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput"))
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2019-02-15 21:10:37 +08:00
|
|
|
If we now pass two stringinput values, our test will run twice:
|
|
|
|
|
|
|
|
.. code-block:: pytest
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2016-06-21 22:16:57 +08:00
|
|
|
$ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
|
2017-11-23 23:33:41 +08:00
|
|
|
.. [100%]
|
2019-09-18 21:11:59 +08:00
|
|
|
2 passed in 0.12s
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2018-11-24 13:41:22 +08:00
|
|
|
Let's also run with a stringinput that will lead to a failing test:
|
|
|
|
|
|
|
|
.. code-block:: pytest
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2016-06-21 22:16:57 +08:00
|
|
|
$ pytest -q --stringinput="!" test_strings.py
|
2017-11-23 23:33:41 +08:00
|
|
|
F [100%]
|
|
|
|
================================= FAILURES =================================
|
|
|
|
___________________________ test_valid_string[!] ___________________________
|
2018-05-18 16:19:46 +08:00
|
|
|
|
2012-10-08 19:19:31 +08:00
|
|
|
stringinput = '!'
|
2018-05-18 16:19:46 +08:00
|
|
|
|
2012-10-08 19:19:31 +08:00
|
|
|
def test_valid_string(stringinput):
|
|
|
|
> assert stringinput.isalpha()
|
2017-05-13 04:17:40 +08:00
|
|
|
E AssertionError: assert False
|
2021-10-04 14:56:26 +08:00
|
|
|
E + where False = <built-in method isalpha of str object at 0xdeadbeef0001>()
|
|
|
|
E + where <built-in method isalpha of str object at 0xdeadbeef0001> = '!'.isalpha
|
2018-05-18 16:19:46 +08:00
|
|
|
|
2019-04-15 22:24:17 +08:00
|
|
|
test_strings.py:4: AssertionError
|
2020-03-11 22:23:25 +08:00
|
|
|
========================= short test summary info ==========================
|
|
|
|
FAILED test_strings.py::test_valid_string[!] - AssertionError: assert False
|
2019-09-18 21:11:59 +08:00
|
|
|
1 failed in 0.12s
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2014-01-18 19:31:33 +08:00
|
|
|
As expected our test function fails.
|
2012-10-09 20:35:17 +08:00
|
|
|
|
|
|
|
If you don't specify a stringinput it will be skipped because
|
|
|
|
``metafunc.parametrize()`` will be called with an empty parameter
|
2018-11-24 13:41:22 +08:00
|
|
|
list:
|
|
|
|
|
|
|
|
.. code-block:: pytest
|
2012-10-09 20:35:17 +08:00
|
|
|
|
2016-06-21 22:16:57 +08:00
|
|
|
$ pytest -q -rs test_strings.py
|
2017-11-23 23:33:41 +08:00
|
|
|
s [100%]
|
|
|
|
========================= short test summary info ==========================
|
2021-10-04 14:56:26 +08:00
|
|
|
SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at /home/sweet/project/test_strings.py:2
|
2019-09-18 21:11:59 +08:00
|
|
|
1 skipped in 0.12s
|
2012-10-07 19:06:17 +08:00
|
|
|
|
2018-05-18 16:19:46 +08:00
|
|
|
Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across
|
2017-09-10 08:50:45 +08:00
|
|
|
those sets cannot be duplicated, otherwise an error will be raised.
|
|
|
|
|
2017-11-10 05:25:30 +08:00
|
|
|
More examples
|
|
|
|
-------------
|
|
|
|
|
2012-10-08 19:19:31 +08:00
|
|
|
For further examples, you might want to look at :ref:`more
|
|
|
|
parametrization examples <paramexamples>`.
|