209 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
			
		
		
	
	
			209 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
| 
 | |
| .. _`test generators`:
 | |
| .. _`parametrizing-tests`:
 | |
| .. _`parametrized test functions`:
 | |
| .. _`parametrize`:
 | |
| 
 | |
| .. _`parametrize-basics`:
 | |
| 
 | |
| Parametrizing fixtures and test functions
 | |
| ==========================================================================
 | |
| 
 | |
| pytest enables test parametrization at several levels:
 | |
| 
 | |
| - :py:func:`pytest.fixture` allows one to :ref:`parametrize fixture
 | |
|   functions <fixture-parametrize>`.
 | |
| 
 | |
| * `@pytest.mark.parametrize`_ allows one to define multiple sets of
 | |
|   arguments and fixtures at the test function or class.
 | |
| 
 | |
| * `pytest_generate_tests`_ allows one to define custom parametrization
 | |
|   schemes or extensions.
 | |
| 
 | |
| .. _parametrizemark:
 | |
| .. _`@pytest.mark.parametrize`:
 | |
| 
 | |
| 
 | |
| ``@pytest.mark.parametrize``: parametrizing test functions
 | |
| ---------------------------------------------------------------------
 | |
| 
 | |
| .. regendoc: wipe
 | |
| 
 | |
| .. versionadded:: 2.2
 | |
| .. versionchanged:: 2.4
 | |
|     Several improvements.
 | |
| 
 | |
| The builtin :ref:`pytest.mark.parametrize ref` decorator enables
 | |
| parametrization of arguments for a test function.  Here is a typical example
 | |
| of a test function that implements checking that a certain input leads
 | |
| to an expected output::
 | |
| 
 | |
|     # content of test_expectation.py
 | |
|     import pytest
 | |
|     @pytest.mark.parametrize("test_input,expected", [
 | |
|         ("3+5", 8),
 | |
|         ("2+4", 6),
 | |
|         ("6*9", 42),
 | |
|     ])
 | |
|     def test_eval(test_input, expected):
 | |
|         assert eval(test_input) == expected
 | |
| 
 | |
| Here, the ``@parametrize`` decorator defines three different ``(test_input,expected)``
 | |
| tuples so that the ``test_eval`` function will run three times using
 | |
| them in turn::
 | |
| 
 | |
|     $ pytest
 | |
|     =========================== test session starts ============================
 | |
|     platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
 | |
|     rootdir: $REGENDOC_TMPDIR, inifile:
 | |
|     collected 3 items
 | |
| 
 | |
|     test_expectation.py ..F                                              [100%]
 | |
| 
 | |
|     ================================= FAILURES =================================
 | |
|     ____________________________ test_eval[6*9-42] _____________________________
 | |
| 
 | |
|     test_input = '6*9', expected = 42
 | |
| 
 | |
|         @pytest.mark.parametrize("test_input,expected", [
 | |
|             ("3+5", 8),
 | |
|             ("2+4", 6),
 | |
|             ("6*9", 42),
 | |
|         ])
 | |
|         def test_eval(test_input, expected):
 | |
|     >       assert eval(test_input) == expected
 | |
|     E       AssertionError: assert 54 == 42
 | |
|     E        +  where 54 = eval('6*9')
 | |
| 
 | |
|     test_expectation.py:8: AssertionError
 | |
|     ==================== 1 failed, 2 passed in 0.12 seconds ====================
 | |
| 
 | |
| 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.
 | |
| 
 | |
| Note that you could also use the parametrize marker on a class or a module
 | |
| (see :ref:`mark`) which would invoke several functions with the argument sets.
 | |
| 
 | |
| It is also possible to mark individual test instances within parametrize,
 | |
| for example with the builtin ``mark.xfail``::
 | |
| 
 | |
|     # content of test_expectation.py
 | |
|     import pytest
 | |
|     @pytest.mark.parametrize("test_input,expected", [
 | |
|         ("3+5", 8),
 | |
|         ("2+4", 6),
 | |
|         pytest.param("6*9", 42,
 | |
|                      marks=pytest.mark.xfail),
 | |
|     ])
 | |
|     def test_eval(test_input, expected):
 | |
|         assert eval(test_input) == expected
 | |
| 
 | |
| Let's run this::
 | |
| 
 | |
|     $ pytest
 | |
|     =========================== test session starts ============================
 | |
|     platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
 | |
|     rootdir: $REGENDOC_TMPDIR, inifile:
 | |
|     collected 3 items
 | |
| 
 | |
|     test_expectation.py ..x                                              [100%]
 | |
| 
 | |
|     =================== 2 passed, 1 xfailed in 0.12 seconds ====================
 | |
| 
 | |
| The one parameter set which caused a failure previously now
 | |
| shows up as an "xfailed (expected to fail)" test.
 | |
| 
 | |
| To get all combinations of multiple parametrized arguments you can stack
 | |
| ``parametrize`` decorators::
 | |
| 
 | |
|     import pytest
 | |
|     @pytest.mark.parametrize("x", [0, 1])
 | |
|     @pytest.mark.parametrize("y", [2, 3])
 | |
|     def test_foo(x, y):
 | |
|         pass
 | |
| 
 | |
| This will run the test with the arguments set to ``x=0/y=2``, ``x=1/y=2``,
 | |
| ``x=0/y=3``, and ``x=1/y=3`` exhausting parameters in the order of the decorators.
 | |
| 
 | |
| .. _`pytest_generate_tests`:
 | |
| 
 | |
| Basic ``pytest_generate_tests`` example
 | |
| ---------------------------------------------
 | |
| 
 | |
| Sometimes you may want to implement your own parametrization scheme
 | |
| or implement some dynamism for determining the parameters or scope
 | |
| of a fixture.   For this, you can use the ``pytest_generate_tests`` hook
 | |
| which is called when collecting a test function.  Through the passed in
 | |
| ``metafunc`` object you can inspect the requesting test context and, most
 | |
| importantly, you can call ``metafunc.parametrize()`` to cause
 | |
| parametrization.
 | |
| 
 | |
| For example, let's say we want to run a test taking string inputs which
 | |
| we want to set via a new ``pytest`` command line option.  Let's first write
 | |
| a simple test accepting a ``stringinput`` fixture function argument::
 | |
| 
 | |
|     # content of test_strings.py
 | |
| 
 | |
|     def test_valid_string(stringinput):
 | |
|         assert stringinput.isalpha()
 | |
| 
 | |
| Now we add a ``conftest.py`` file containing the addition of a
 | |
| command line option and the parametrization of our test function::
 | |
| 
 | |
|     # content of conftest.py
 | |
| 
 | |
|     def pytest_addoption(parser):
 | |
|         parser.addoption("--stringinput", action="append", default=[],
 | |
|             help="list of stringinputs to pass to test functions")
 | |
| 
 | |
|     def pytest_generate_tests(metafunc):
 | |
|         if 'stringinput' in metafunc.fixturenames:
 | |
|             metafunc.parametrize("stringinput",
 | |
|                                  metafunc.config.getoption('stringinput'))
 | |
| 
 | |
| If we now pass two stringinput values, our test will run twice::
 | |
| 
 | |
|     $ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
 | |
|     ..                                                                   [100%]
 | |
|     2 passed in 0.12 seconds
 | |
| 
 | |
| Let's also run with a stringinput that will lead to a failing test::
 | |
| 
 | |
|     $ pytest -q --stringinput="!" test_strings.py
 | |
|     F                                                                    [100%]
 | |
|     ================================= FAILURES =================================
 | |
|     ___________________________ test_valid_string[!] ___________________________
 | |
| 
 | |
|     stringinput = '!'
 | |
| 
 | |
|         def test_valid_string(stringinput):
 | |
|     >       assert stringinput.isalpha()
 | |
|     E       AssertionError: assert False
 | |
|     E        +  where False = <built-in method isalpha of str object at 0xdeadbeef>()
 | |
|     E        +    where <built-in method isalpha of str object at 0xdeadbeef> = '!'.isalpha
 | |
| 
 | |
|     test_strings.py:3: AssertionError
 | |
|     1 failed in 0.12 seconds
 | |
| 
 | |
| As expected our test function fails.
 | |
| 
 | |
| If you don't specify a stringinput it will be skipped because
 | |
| ``metafunc.parametrize()`` will be called with an empty parameter
 | |
| list::
 | |
| 
 | |
|     $ pytest -q -rs test_strings.py
 | |
|     s                                                                    [100%]
 | |
|     ========================= short test summary info ==========================
 | |
|     SKIP [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
 | |
|     1 skipped in 0.12 seconds
 | |
| 
 | |
| Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across
 | |
| those sets cannot be duplicated, otherwise an error will be raised.
 | |
| 
 | |
| More examples
 | |
| -------------
 | |
| 
 | |
| For further examples, you might want to look at :ref:`more
 | |
| parametrization examples <paramexamples>`.
 |