Skip to content

Commit 7939213

Browse files
nicoddemusbluetech
andauthored
Integrate pytest-subtests (#13738)
Fixes #1367 Fixes pytest-dev/pytest-subtests#71 --------- Co-authored-by: Ran Benita <[email protected]>
1 parent 5eaad82 commit 7939213

File tree

17 files changed

+1727
-24
lines changed

17 files changed

+1727
-24
lines changed

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ exclude_lines =
2525
^\s*raise NotImplementedError\b
2626
^\s*return NotImplemented\b
2727
^\s*assert False(,|$)
28+
^\s*case unreachable:
2829
^\s*assert_never\(
2930

3031
^\s*if TYPE_CHECKING:

changelog/1367.feature.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
**Support for subtests** has been added.
2+
3+
:ref:`subtests <subtests>` are an alternative to parametrization, useful in situations where the parametrization values are not all known at collection time.
4+
5+
**Example**
6+
7+
.. code-block:: python
8+
9+
def contains_docstring(p: Path) -> bool:
10+
"""Return True if the given Python file contains a top-level docstring."""
11+
...
12+
13+
14+
def test_py_files_contain_docstring(subtests: pytest.Subtests) -> None:
15+
for path in Path.cwd().glob("*.py"):
16+
with subtests.test(path=str(path)):
17+
assert contains_docstring(path)
18+
19+
20+
Each assert failure or error is caught by the context manager and reported individually, giving a clear picture of all files that are missing a docstring.
21+
22+
In addition, :meth:`unittest.TestCase.subTest` is now also supported.
23+
24+
This feature was originally implemented as a separate plugin in `pytest-subtests <https://github.com/pytest-dev/pytest-subtests>`__, but since then has been merged into the core.
25+
26+
.. note::
27+
28+
This feature is experimental and will likely evolve in future releases. By that we mean that we might change how subtests are reported on failure, but the functionality and how to use it are stable.

doc/en/how-to/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Core pytest functionality
1616
fixtures
1717
mark
1818
parametrize
19+
subtests
1920
tmp_path
2021
monkeypatch
2122
doctest

doc/en/how-to/parametrize.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ pytest enables test parametrization at several levels:
2020
* `pytest_generate_tests`_ allows one to define custom parametrization
2121
schemes or extensions.
2222

23+
24+
.. note::
25+
26+
See :ref:`subtests` for an alternative to parametrization.
27+
2328
.. _parametrizemark:
2429
.. _`@pytest.mark.parametrize`:
2530

@@ -203,6 +208,7 @@ To get all combinations of multiple parametrized arguments you can stack
203208
This will run the test with the arguments set to ``x=0/y=2``, ``x=1/y=2``,
204209
``x=0/y=3``, and ``x=1/y=3`` exhausting parameters in the order of the decorators.
205210

211+
206212
.. _`pytest_generate_tests`:
207213

208214
Basic ``pytest_generate_tests`` example

doc/en/how-to/subtests.rst

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
.. _subtests:
2+
3+
How to use subtests
4+
===================
5+
6+
.. versionadded:: 9.0
7+
8+
.. note::
9+
10+
This feature is experimental. Its behavior, particularly how failures are reported, may evolve in future releases. However, the core functionality and usage are considered stable.
11+
12+
pytest allows for grouping assertions within a normal test, known as *subtests*.
13+
14+
Subtests are an alternative to parametrization, particularly useful when the exact parametrization values are not known at collection time.
15+
16+
17+
.. code-block:: python
18+
19+
# content of test_subtest.py
20+
21+
22+
def test(subtests):
23+
for i in range(5):
24+
with subtests.test(msg="custom message", i=i):
25+
assert i % 2 == 0
26+
27+
Each assertion failure or error is caught by the context manager and reported individually:
28+
29+
.. code-block:: pytest
30+
31+
$ pytest -q test_subtest.py
32+
33+
34+
In the output above:
35+
36+
* Subtest failures are reported as ``SUBFAILED``.
37+
* Subtests are reported first and the "top-level" test is reported at the end on its own.
38+
39+
Note that it is possible to use ``subtests`` multiple times in the same test, or even mix and match with normal assertions
40+
outside the ``subtests.test`` block:
41+
42+
.. code-block:: python
43+
44+
def test(subtests):
45+
for i in range(5):
46+
with subtests.test("stage 1", i=i):
47+
assert i % 2 == 0
48+
49+
assert func() == 10
50+
51+
for i in range(10, 20):
52+
with subtests.test("stage 2", i=i):
53+
assert i % 2 == 0
54+
55+
.. note::
56+
57+
See :ref:`parametrize` for an alternative to subtests.
58+
59+
60+
Verbosity
61+
---------
62+
63+
By default, only **subtest failures** are shown. Higher verbosity levels (``-v``) will also show progress output for **passed** subtests.
64+
65+
It is possible to control the verbosity of subtests by setting :confval:`verbosity_subtests`.
66+
67+
68+
Typing
69+
------
70+
71+
:class:`pytest.Subtests` is exported so it can be used in type annotations:
72+
73+
.. code-block:: python
74+
75+
def test(subtests: pytest.Subtests) -> None: ...
76+
77+
.. _parametrize_vs_subtests:
78+
79+
Parametrization vs Subtests
80+
---------------------------
81+
82+
While :ref:`traditional pytest parametrization <parametrize>` and ``subtests`` are similar, they have important differences and use cases.
83+
84+
85+
Parametrization
86+
~~~~~~~~~~~~~~~
87+
88+
* Happens at collection time.
89+
* Generates individual tests.
90+
* Parametrized tests can be referenced from the command line.
91+
* Plays well with plugins that handle test execution, such as ``--last-failed``.
92+
* Ideal for decision table testing.
93+
94+
Subtests
95+
~~~~~~~~
96+
97+
* Happen during test execution.
98+
* Are not known at collection time.
99+
* Can be generated dynamically.
100+
* Cannot be referenced individually from the command line.
101+
* Plugins that handle test execution cannot target individual subtests.
102+
* An assertion failure inside a subtest does not interrupt the test, letting users see all failures in the same report.
103+
104+
105+
.. note::
106+
107+
This feature was originally implemented as a separate plugin in `pytest-subtests <https://github.com/pytest-dev/pytest-subtests>`__, but since ``9.0`` has been merged into the core.
108+
109+
The core implementation should be compatible to the plugin implementation, except it does not contain custom command-line options to control subtest output.

doc/en/how-to/unittest.rst

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,14 @@ their ``test`` methods in ``test_*.py`` or ``*_test.py`` files.
2222

2323
Almost all ``unittest`` features are supported:
2424

25-
* ``@unittest.skip`` style decorators;
26-
* ``setUp/tearDown``;
27-
* ``setUpClass/tearDownClass``;
28-
* ``setUpModule/tearDownModule``;
25+
* :func:`unittest.skip`/:func:`unittest.skipIf` style decorators
26+
* :meth:`unittest.TestCase.setUp`/:meth:`unittest.TestCase.tearDown`
27+
* :meth:`unittest.TestCase.setUpClass`/:meth:`unittest.TestCase.tearDownClass`
28+
* :func:`unittest.setUpModule`/:func:`unittest.tearDownModule`
29+
* :meth:`unittest.TestCase.subTest` (since version ``9.0``)
2930

30-
.. _`pytest-subtests`: https://github.com/pytest-dev/pytest-subtests
3131
.. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol
3232

33-
Additionally, :ref:`subtests <python:subtests>` are supported by the
34-
`pytest-subtests`_ plugin.
35-
3633
Up to this point pytest does not have support for the following features:
3734

3835
* `load_tests protocol`_;

doc/en/reference/fixtures.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ Built-in fixtures
5252
:fixture:`pytestconfig`
5353
Access to configuration values, pluginmanager and plugin hooks.
5454

55+
:fixture:`subtests`
56+
Enable declaring subtests inside test functions.
57+
5558
:fixture:`record_property`
5659
Add extra properties to the test.
5760

doc/en/reference/reference.rst

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,19 @@ The ``request`` fixture is a special fixture providing information of the reques
572572
:members:
573573

574574

575+
.. fixture:: subtests
576+
577+
subtests
578+
~~~~~~~~
579+
580+
The ``subtests`` fixture enables declaring subtests inside test functions.
581+
582+
**Tutorial**: :ref:`subtests`
583+
584+
.. autoclass:: pytest.Subtests()
585+
:members:
586+
587+
575588
.. fixture:: testdir
576589

577590
testdir
@@ -2600,8 +2613,35 @@ passed multiple times. The expected format is ``name=value``. For example::
26002613
[pytest]
26012614
verbosity_assertions = 2
26022615
2603-
Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
2604-
"auto" can be used to explicitly use the global verbosity level.
2616+
If not set, defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
2617+
``"auto"`` can be used to explicitly use the global verbosity level.
2618+
2619+
2620+
.. confval:: verbosity_subtests
2621+
2622+
Set the verbosity level specifically for **passed** subtests.
2623+
2624+
.. tab:: toml
2625+
2626+
.. code-block:: toml
2627+
2628+
[pytest]
2629+
verbosity_subtests = 1
2630+
2631+
.. tab:: ini
2632+
2633+
.. code-block:: ini
2634+
2635+
[pytest]
2636+
verbosity_subtests = 1
2637+
2638+
A value of ``1`` or higher will show output for **passed** subtests (**failed** subtests are always reported).
2639+
Passed subtests output can be suppressed with the value ``0``, which overwrites the ``-v`` command-line option.
2640+
2641+
If not set, defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
2642+
``"auto"`` can be used to explicitly use the global verbosity level.
2643+
2644+
See also: :ref:`subtests`.
26052645

26062646

26072647
.. confval:: verbosity_test_cases
@@ -2622,8 +2662,8 @@ passed multiple times. The expected format is ``name=value``. For example::
26222662
[pytest]
26232663
verbosity_test_cases = 2
26242664
2625-
Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
2626-
"auto" can be used to explicitly use the global verbosity level.
2665+
If not set, defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
2666+
``"auto"`` can be used to explicitly use the global verbosity level.
26272667

26282668

26292669
.. _`command-line-flags`:

src/_pytest/config/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ def directory_arg(path: str, optname: str) -> str:
282282
"logging",
283283
"reports",
284284
"faulthandler",
285+
"subtests",
285286
)
286287

287288
builtin_plugins = {
@@ -1878,6 +1879,9 @@ def getvalueorskip(self, name: str, path=None):
18781879
VERBOSITY_ASSERTIONS: Final = "assertions"
18791880
#: Verbosity type for test case execution (see :confval:`verbosity_test_cases`).
18801881
VERBOSITY_TEST_CASES: Final = "test_cases"
1882+
#: Verbosity type for failed subtests (see :confval:`verbosity_subtests`).
1883+
VERBOSITY_SUBTESTS: Final = "subtests"
1884+
18811885
_VERBOSITY_INI_DEFAULT: Final = "auto"
18821886

18831887
def get_verbosity(self, verbosity_type: str | None = None) -> int:

src/_pytest/deprecated.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"pytest_catchlog",
2626
"pytest_capturelog",
2727
"pytest_faulthandler",
28+
"pytest_subtests",
2829
}
2930

3031

0 commit comments

Comments
 (0)