Skip to content

Commit 5d06ac7

Browse files
committed
Add a changelog
1 parent 6910ee1 commit 5d06ac7

File tree

3 files changed

+79
-1
lines changed

3 files changed

+79
-1
lines changed

changelog/13233.feature.rst

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:hook:`pytest_generate_tests` hooks and :ref:`pytest.mark.parametrize <pytest.mark.parametrize ref>`
2+
can now depend on previous parametrizations instead of generating a Cartesian product of parameter sets.
3+
See details in :ref:`parametrize_dependent`.
4+
5+
For example, a :hook:`pytest_generate_tests` hook that relies on marks can now account for all the marks correctly.

doc/en/example/parametrize.rst

+67
Original file line numberDiff line numberDiff line change
@@ -688,3 +688,70 @@ For example:
688688
In the example above, the first three test cases should run without any
689689
exceptions, while the fourth should raise a ``ZeroDivisionError`` exception,
690690
which is expected by pytest.
691+
692+
.. _`parametrize_dependent`:
693+
694+
Adding parameters depending on previous parametrizations
695+
--------------------------------------------------------------------
696+
697+
By default, :hook:`pytest_generate_tests` hooks and
698+
:ref:`pytest.mark.parametrize <pytest.mark.parametrize ref>` generate
699+
a Cartesian product of parameter sets in case of multiple parametrizations,
700+
see :ref:`parametrize-basics` for some examples.
701+
702+
Sometimes, values of some parameters need to be generated based on values
703+
of previous parameters or based on their associated marks.
704+
705+
In such cases ``parametrize`` can be passed a callable for ``argvalues``,
706+
which will decide how to further parametrize each test instance:
707+
708+
.. code-block:: python
709+
710+
# content of test_parametrize_dependent.py
711+
def pytest_generate_tests(metafunc: pytest.Metafunc):
712+
if "bar" in metafunc.fixturenames:
713+
# parametrize "bar" arg based on "bar_params" mark
714+
base_bar_marks = list(metafunc.definition.iter_markers("bar_params"))
715+
716+
def gen_params(callspec: pytest.CallSpec):
717+
# collect all marks
718+
bar_marks = base_bar_marks + [
719+
mark for mark in callspec.marks if mark.name == "bar_params"
720+
]
721+
# collect all args from all marks
722+
return [arg for mark in bar_marks for arg in mark.args]
723+
724+
metafunc.parametrize("bar", gen_params)
725+
726+
727+
@pytest.mark.bar_params("x")
728+
@pytest.mark.parametrize(
729+
"foo",
730+
[
731+
"a",
732+
pytest.param("b", marks=[pytest.mark.bar_params("y", "z")]),
733+
pytest.param("c", marks=[pytest.mark.bar_params("w")]),
734+
],
735+
)
736+
def test_function(foo, bar):
737+
pass
738+
739+
Running ``pytest`` with verbose mode outputs:
740+
741+
.. code-block:: pytest
742+
743+
$ pytest -v
744+
=========================== test session starts ============================
745+
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
746+
cachedir: .pytest_cache
747+
rootdir: /home/sweet/project
748+
collecting ... collected 6 items
749+
750+
test_parametrize_dependent.py::test_function[a-x] PASSED [ 16%]
751+
test_parametrize_dependent.py::test_function[b-x] PASSED [ 33%]
752+
test_parametrize_dependent.py::test_function[b-y] PASSED [ 50%]
753+
test_parametrize_dependent.py::test_function[b-z] PASSED [ 66%]
754+
test_parametrize_dependent.py::test_function[c-x] PASSED [ 83%]
755+
test_parametrize_dependent.py::test_function[c-w] PASSED [100%]
756+
757+
============================ 6 passed in 0.12s =============================

src/_pytest/python.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -1181,11 +1181,17 @@ def parametrize(
11811181
The list of argvalues determines how often a test is invoked with
11821182
different argument values.
11831183
1184-
If only one argname was specified argvalues is a list of values.
1184+
If only one argname was specified, argvalues is a list of values.
11851185
If N argnames were specified, argvalues must be a list of
11861186
N-tuples, where each tuple-element specifies a value for its
11871187
respective argname.
1188+
:func:`pytest.param` can be used instead of tuples for additional
1189+
control over parameter sets.
1190+
1191+
Parameter sets can be generated depending on previous
1192+
parametrizations, see :ref:`parametrize_dependent`.
11881193
:type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object] | Callable
1194+
11891195
:param indirect:
11901196
A list of arguments' names (subset of argnames) or a boolean.
11911197
If True the list contains all names from the argnames. Each

0 commit comments

Comments
 (0)