Skip to content

Commit c988e49

Browse files
authored
Warn when test functions return other than None (#9956)
Closes #7337
1 parent 31f9e5b commit c988e49

File tree

8 files changed

+75
-0
lines changed

8 files changed

+75
-0
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Ceridwen
6363
Charles Cloud
6464
Charles Machalow
6565
Charnjit SiNGH (CCSJ)
66+
Cheuk Ting Ho
6667
Chris Lamb
6768
Chris NeJame
6869
Chris Rose

changelog/7337.improvement.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`.

doc/en/deprecations.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,47 @@ or ``pytest.warns(Warning)``.
252252

253253
See :ref:`warns use cases` for examples.
254254

255+
256+
Returning non-None value in test functions
257+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
258+
259+
.. deprecated:: 7.2
260+
261+
A :class:`pytest.PytestReturnNotNoneWarning` is now emitted if a test function returns something other than `None`.
262+
263+
This prevents a common mistake among beginners that expect that returning a `bool` would cause a test to pass or fail, for example:
264+
265+
.. code-block:: python
266+
267+
@pytest.mark.parametrize(
268+
["a", "b", "result"],
269+
[
270+
[1, 2, 5],
271+
[2, 3, 8],
272+
[5, 3, 18],
273+
],
274+
)
275+
def test_foo(a, b, result):
276+
return foo(a, b) == result
277+
278+
Given that pytest ignores the return value, this might be surprising that it will never fail.
279+
280+
The proper fix is to change the `return` to an `assert`:
281+
282+
.. code-block:: python
283+
284+
@pytest.mark.parametrize(
285+
["a", "b", "result"],
286+
[
287+
[1, 2, 5],
288+
[2, 3, 8],
289+
[5, 3, 18],
290+
],
291+
)
292+
def test_foo(a, b, result):
293+
assert foo(a, b) == result
294+
295+
255296
The ``--strict`` command-line option
256297
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
257298

doc/en/reference/reference.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,9 @@ Custom warnings generated in some situations such as improper usage or deprecate
11301130
.. autoclass:: pytest.PytestExperimentalApiWarning
11311131
:show-inheritance:
11321132

1133+
.. autoclass:: pytest.PytestReturnNotNoneWarning
1134+
:show-inheritance:
1135+
11331136
.. autoclass:: pytest.PytestUnhandledCoroutineWarning
11341137
:show-inheritance:
11351138

src/_pytest/python.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,12 @@
7777
from _pytest.pathlib import visit
7878
from _pytest.scope import Scope
7979
from _pytest.warning_types import PytestCollectionWarning
80+
from _pytest.warning_types import PytestReturnNotNoneWarning
8081
from _pytest.warning_types import PytestUnhandledCoroutineWarning
8182

8283
if TYPE_CHECKING:
8384
from typing_extensions import Literal
85+
8486
from _pytest.scope import _ScopeName
8587

8688

@@ -192,6 +194,13 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
192194
result = testfunction(**testargs)
193195
if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
194196
async_warn_and_skip(pyfuncitem.nodeid)
197+
elif result is not None:
198+
warnings.warn(
199+
PytestReturnNotNoneWarning(
200+
f"Expected None, but {pyfuncitem.nodeid} returned {result!r}, which will be an error in a "
201+
"future version of pytest. Did you mean to use `assert` instead of `return`?"
202+
)
203+
)
195204
return True
196205

197206

src/_pytest/warning_types.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ class PytestRemovedIn8Warning(PytestDeprecationWarning):
5555
__module__ = "pytest"
5656

5757

58+
@final
59+
class PytestReturnNotNoneWarning(PytestDeprecationWarning):
60+
"""Warning emitted when a test function is returning value other than None."""
61+
62+
__module__ = "pytest"
63+
64+
5865
@final
5966
class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
6067
"""Warning category used to denote experiments in pytest.

src/pytest/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
from _pytest.warning_types import PytestDeprecationWarning
7070
from _pytest.warning_types import PytestExperimentalApiWarning
7171
from _pytest.warning_types import PytestRemovedIn8Warning
72+
from _pytest.warning_types import PytestReturnNotNoneWarning
7273
from _pytest.warning_types import PytestUnhandledCoroutineWarning
7374
from _pytest.warning_types import PytestUnhandledThreadExceptionWarning
7475
from _pytest.warning_types import PytestUnknownMarkWarning
@@ -127,6 +128,7 @@
127128
"PytestDeprecationWarning",
128129
"PytestExperimentalApiWarning",
129130
"PytestRemovedIn8Warning",
131+
"PytestReturnNotNoneWarning",
130132
"Pytester",
131133
"PytestPluginManager",
132134
"PytestUnhandledCoroutineWarning",

testing/acceptance_test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,3 +1292,14 @@ def test_no_brokenpipeerror_message(pytester: Pytester) -> None:
12921292

12931293
# Cleanup.
12941294
popen.stderr.close()
1295+
1296+
1297+
def test_function_return_non_none_warning(testdir) -> None:
1298+
testdir.makepyfile(
1299+
"""
1300+
def test_stuff():
1301+
return "something"
1302+
"""
1303+
)
1304+
res = testdir.runpytest()
1305+
res.stdout.fnmatch_lines(["*Did you mean to use `assert` instead of `return`?*"])

0 commit comments

Comments
 (0)