diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst
index fcbbc2491..62a31fdad 100644
--- a/doc/en/deprecations.rst
+++ b/doc/en/deprecations.rst
@@ -25,7 +25,7 @@ Support for tests written for nose
Support for running tests written for `nose `__ is now deprecated.
-`nose` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills
+``nose`` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills
over the code base (see :issue:`9886` for more details).
setup/teardown
@@ -70,11 +70,12 @@ Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`x
...
+This is easy to do in an entire code base by doing a simple find/replace.
+
@with_setup
^^^^^^^^^^^
-Code using `@with_setup `_ will also need to be ported to a supported
-pytest style:
+Code using `@with_setup `_ such as this:
.. code-block:: python
@@ -93,7 +94,7 @@ pytest style:
def test_foo():
...
-One way to do it is using a fixture:
+Will also need to be ported to a supported pytest style. One way to do it is using a fixture:
.. code-block:: python
diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py
index 38af4b24c..fc0d9b149 100644
--- a/src/_pytest/deprecated.py
+++ b/src/_pytest/deprecated.py
@@ -25,7 +25,16 @@ DEPRECATED_EXTERNAL_PLUGINS = {
NOSE_SUPPORT = UnformattedWarning(
PytestRemovedIn8Warning,
"Support for nose tests is deprecated and will be removed in a future release.\n"
- "{nodeid} is using nose method: `{method}` ({stage})",
+ "{nodeid} is using nose method: `{method}` ({stage})\n"
+ "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose",
+)
+
+NOSE_SUPPORT_METHOD = UnformattedWarning(
+ PytestRemovedIn8Warning,
+ "Support for nose tests is deprecated and will be removed in a future release.\n"
+ "{nodeid} is using nose-specific method: `{method}(self)`\n"
+ "To remove this warning, rename it to `{method}_method(self)`\n"
+ "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose",
)
diff --git a/src/_pytest/python.py b/src/_pytest/python.py
index 91054f370..8cad83b8f 100644
--- a/src/_pytest/python.py
+++ b/src/_pytest/python.py
@@ -59,6 +59,7 @@ from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
from _pytest.deprecated import INSTANCE_COLLECTOR
+from _pytest.deprecated import NOSE_SUPPORT_METHOD
from _pytest.fixtures import FuncFixtureInfo
from _pytest.main import Session
from _pytest.mark import MARK_GEN
@@ -872,19 +873,23 @@ class Class(PyCollector):
"""Inject a hidden autouse, function scoped fixture into the collected class object
that invokes setup_method/teardown_method if either or both are available.
- Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
+ Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with
other fixtures (#517).
"""
has_nose = self.config.pluginmanager.has_plugin("nose")
setup_name = "setup_method"
setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
+ emit_nose_setup_warning = False
if setup_method is None and has_nose:
setup_name = "setup"
+ emit_nose_setup_warning = True
setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
teardown_name = "teardown_method"
teardown_method = getattr(self.obj, teardown_name, None)
+ emit_nose_teardown_warning = False
if teardown_method is None and has_nose:
teardown_name = "teardown"
+ emit_nose_teardown_warning = True
teardown_method = getattr(self.obj, teardown_name, None)
if setup_method is None and teardown_method is None:
return
@@ -900,10 +905,24 @@ class Class(PyCollector):
if setup_method is not None:
func = getattr(self, setup_name)
_call_with_optional_argument(func, method)
+ if emit_nose_setup_warning:
+ warnings.warn(
+ NOSE_SUPPORT_METHOD.format(
+ nodeid=request.node.nodeid, method="setup"
+ ),
+ stacklevel=2,
+ )
yield
if teardown_method is not None:
func = getattr(self, teardown_name)
_call_with_optional_argument(func, method)
+ if emit_nose_teardown_warning:
+ warnings.warn(
+ NOSE_SUPPORT_METHOD.format(
+ nodeid=request.node.nodeid, method="teardown"
+ ),
+ stacklevel=2,
+ )
self.obj.__pytest_setup_method = xunit_setup_method_fixture
diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py
index dcc397f8a..795b52a3f 100644
--- a/testing/deprecated_test.py
+++ b/testing/deprecated_test.py
@@ -233,7 +233,8 @@ def test_importing_instance_is_deprecated(pytester: Pytester) -> None:
from _pytest.python import Instance # noqa: F401
-def test_nose_deprecated(pytester: Pytester) -> None:
+@pytest.mark.filterwarnings("default")
+def test_nose_deprecated_with_setup(pytester: Pytester) -> None:
pytest.importorskip("nose")
pytester.makepyfile(
"""
@@ -253,9 +254,39 @@ def test_nose_deprecated(pytester: Pytester) -> None:
output = pytester.runpytest()
message = [
"*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.",
- "*test_nose_deprecated.py::test_omits_warnings is using nose method: `setup_fn_no_op` (setup)",
+ "*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `setup_fn_no_op` (setup)",
"*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.",
- "*test_nose_deprecated.py::test_omits_warnings is using nose method: `teardown_fn_no_op` (teardown)",
+ "*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `teardown_fn_no_op` (teardown)",
+ ]
+ output.stdout.fnmatch_lines(message)
+ output.assert_outcomes(passed=1)
+
+
+@pytest.mark.filterwarnings("default")
+def test_nose_deprecated_setup_teardown(pytester: Pytester) -> None:
+ pytest.importorskip("nose")
+ pytester.makepyfile(
+ """
+ class Test:
+
+ def setup(self):
+ ...
+
+ def teardown(self):
+ ...
+
+ def test(self):
+ ...
+ """
+ )
+ output = pytester.runpytest()
+ message = [
+ "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.",
+ "*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `setup(self)`",
+ "*To remove this warning, rename it to `setup_method(self)`",
+ "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.",
+ "*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `teardown(self)`",
+ "*To remove this warning, rename it to `teardown_method(self)`",
]
output.stdout.fnmatch_lines(message)
output.assert_outcomes(passed=1)