-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Integrate pytest-subtests #13738
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Integrate pytest-subtests #13738
Changes from all commits
c3dc9f4
e856b0b
596a4dc
8f58851
40b6cb2
b569c93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
**Support for subtests** has been added. | ||
|
||
:ref:`subtests <subtests>` are an alternative to parametrization, useful in situations where test setup is expensive or the parametrization values are not all known at collection time. | ||
|
||
**Example** | ||
|
||
.. code-block:: python | ||
|
||
def test(subtests): | ||
for i in range(5): | ||
with subtests.test(msg="custom message", i=i): | ||
assert i % 2 == 0 | ||
|
||
|
||
Each assert failure or error is caught by the context manager and reported individually. | ||
|
||
In addition, :meth:`unittest.TestCase.subTest` is now also supported. | ||
|
||
.. note:: | ||
|
||
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ Core pytest functionality | |
fixtures | ||
mark | ||
parametrize | ||
subtests | ||
tmp_path | ||
monkeypatch | ||
doctest | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
.. _subtests: | ||
|
||
How to use subtests | ||
=================== | ||
|
||
.. versionadded:: 9.0 | ||
|
||
.. note:: | ||
|
||
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. | ||
|
||
pytest allows for grouping assertions within a normal test, known as *subtests*. | ||
|
||
Subtests are an alternative to parametrization, particularly useful when test setup is expensive or when the exact parametrization values are not known at collection time. | ||
|
||
|
||
.. code-block:: python | ||
|
||
# content of test_subtest.py | ||
|
||
|
||
def test(subtests): | ||
for i in range(5): | ||
with subtests.test(msg="custom message", i=i): | ||
assert i % 2 == 0 | ||
|
||
Each assertion failure or error is caught by the context manager and reported individually: | ||
|
||
.. code-block:: pytest | ||
|
||
$ pytest -q test_subtest.py | ||
|
||
|
||
Note that it is possible to use ``subtests`` multiple times in the same test, or even mix and match with normal assertions | ||
outside the ``subtests.test`` block: | ||
|
||
.. code-block:: python | ||
|
||
def test(subtests): | ||
for i in range(5): | ||
with subtests.test(msg="stage 1", i=i): | ||
assert i % 2 == 0 | ||
|
||
assert func() == 10 | ||
|
||
for i in range(10, 20): | ||
with subtests.test(msg="stage 2", i=i): | ||
assert i % 2 == 0 | ||
|
||
.. note:: | ||
|
||
See :ref:`parametrize` for an alternative to subtests. | ||
|
||
|
||
Typing | ||
------ | ||
|
||
:class:`pytest.SubTests` is exported so it can be used in type annotations: | ||
|
||
.. code-block:: python | ||
|
||
def test(subtests: pytest.SubTests) -> None: ... | ||
|
||
.. _parametrize_vs_subtests: | ||
|
||
Parametrization vs Subtests | ||
--------------------------- | ||
|
||
While :ref:`traditional pytest parametrization <parametrize>` and ``subtests`` are similar, they have important differences and use cases. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Continuing from the comment above, I can see two ways we can approach this: 1 - Subtests are for "runtime parametrization"Per comment above, subtests are useful when you have some data dynamically fetched in the test and want individualized reporting for each data value. The idea here is that parametrization should be the go-to tool, but we offer this subtest tool for this particular scenario. 2 - Subtests are for "sub-testing"By which I mean, subtests are for when you have one conceptual test, i.e. consider it a complete whole, but just want to break down its reporting to parts. How do you see it? The reason I'm asking is that it can affect how we document the feature, what we recommend, etc. |
||
|
||
Parametrization | ||
~~~~~~~~~~~~~~~ | ||
|
||
* Happens at collection time. | ||
* Generates individual tests. | ||
* Parametrized tests can be referenced from the command line. | ||
* Plays well with plugins that handle test execution, such as ``--last-failed``. | ||
* Ideal for decision table testing. | ||
|
||
Subtests | ||
~~~~~~~~ | ||
|
||
* Happen during test execution. | ||
* Are not known at collection time. | ||
* Can be generated dynamically. | ||
* Cannot be referenced individually from the command line. | ||
* Plugins that handle test execution cannot target individual subtests. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ | |
"pytest_catchlog", | ||
"pytest_capturelog", | ||
"pytest_faulthandler", | ||
"pytest_subtests", | ||
} | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For expensive test setup, pytest has a solution in the form of scoped fixtures. Since regular parametrization is advantageous, e.g. allowing each param value to be tested individually by nodeid, I think we should not encourage subtests when there's a decent parametrization alternative.
The "values not known at collection time" case is what I think we should emphasize. There is indeed inherently no way to do "dynamic parametrization" after collection.