Deprecate the 'message' parameter of pytest.raises

Fix #3974
This commit is contained in:
Bruno Oliveira 2018-12-12 19:12:44 -02:00
parent 110fe2473f
commit 5b83417afc
7 changed files with 77 additions and 37 deletions

View File

@ -0,0 +1,8 @@
Passing the ``message`` parameter of ``pytest.raises`` now issues a ``DeprecationWarning``.
It is a common mistake to think this parameter will match the exception message, while in fact
it only serves to provide a custom message in case the ``pytest.raises`` check fails. To avoid this
mistake and because it is believed to be little used, pytest is deprecating it without providing
an alternative for the moment.
If you have concerns about this, please comment on `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__.

View File

@ -14,6 +14,19 @@ Below is a complete list of all pytest features which are considered deprecated.
:class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using :class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using
:ref:`standard warning filters <warnings>`. :ref:`standard warning filters <warnings>`.
``"message"`` parameter of ``pytest.raises``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 4.1
It is a common mistake to think this parameter will match the exception message, while in fact
it only serves to provide a custom message in case the ``pytest.raises`` check fails. To avoid this
mistake and because it is believed to be little used, pytest is deprecating it without providing
an alternative for the moment.
If you have concerns about this, please comment on `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__.
``pytest.config`` global ``pytest.config`` global
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -51,6 +51,13 @@ GETFUNCARGVALUE = RemovedInPytest4Warning(
"getfuncargvalue is deprecated, use getfixturevalue" "getfuncargvalue is deprecated, use getfixturevalue"
) )
RAISES_MESSAGE_PARAMETER = PytestDeprecationWarning(
"The 'message' parameter is deprecated.\n"
"(did you mean to use `match='some regex'` to check the exception message?)\n"
"Please comment on https://github.com/pytest-dev/pytest/issues/3974 "
"if you have concerns about removal of this parameter."
)
RESULT_LOG = PytestDeprecationWarning( RESULT_LOG = PytestDeprecationWarning(
"--result-log is deprecated and scheduled for removal in pytest 5.0.\n" "--result-log is deprecated and scheduled for removal in pytest 5.0.\n"
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information." "See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."

View File

@ -13,11 +13,11 @@ from six.moves import filterfalse
from six.moves import zip from six.moves import zip
import _pytest._code import _pytest._code
from _pytest import deprecated
from _pytest.compat import isclass from _pytest.compat import isclass
from _pytest.compat import Mapping from _pytest.compat import Mapping
from _pytest.compat import Sequence from _pytest.compat import Sequence
from _pytest.compat import STRING_TYPES from _pytest.compat import STRING_TYPES
from _pytest.deprecated import RAISES_EXEC
from _pytest.outcomes import fail from _pytest.outcomes import fail
BASE_TYPE = (type, STRING_TYPES) BASE_TYPE = (type, STRING_TYPES)
@ -551,29 +551,47 @@ def _is_numpy_array(obj):
def raises(expected_exception, *args, **kwargs): def raises(expected_exception, *args, **kwargs):
r""" r"""
Assert that a code block/function call raises ``expected_exception`` Assert that a code block/function call raises ``expected_exception``
and raise a failure exception otherwise. or raise a failure exception otherwise.
:arg message: if specified, provides a custom failure message if the :kwparam match: if specified, asserts that the exception matches a text or regex
exception is not raised
:arg match: if specified, asserts that the exception matches a text or regex
This helper produces a ``ExceptionInfo()`` object (see below). :kwparam message: **(deprecated since 4.1)** if specified, provides a custom failure message
if the exception is not raised
You may use this function as a context manager:: .. currentmodule:: _pytest._code
Use ``pytest.raises`` as a context manager, which will capture the exception of the given
type::
>>> with raises(ZeroDivisionError): >>> with raises(ZeroDivisionError):
... 1/0 ... 1/0
.. versionchanged:: 2.10 If the code block does not raise the expected exception (``ZeroDivisionError`` in the example
above), or no exception at all, the check will fail instead.
In the context manager form you may use the keyword argument You can also use the keyword argument ``match`` to assert that the
``message`` to specify a custom failure message:: exception matches a text or regex::
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"): >>> with raises(ValueError, match='must be 0 or None'):
... pass ... raise ValueError("value must be 0 or None")
Traceback (most recent call last):
... >>> with raises(ValueError, match=r'must be \d+$'):
Failed: Expecting ZeroDivisionError ... raise ValueError("value must be 42")
The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
details of the captured exception::
>>> with raises(ValueError) as exc_info:
... raise ValueError("value must be 42")
>>> assert exc_info.type is ValueError
>>> assert exc_info.value.args[0] == "value must be 42"
.. deprecated:: 4.1
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message that will be displayed
in case the ``pytest.raises`` check fails. This has been deprecated as it
is considered error prone as users often mean to use ``match`` instead.
.. note:: .. note::
@ -587,7 +605,7 @@ def raises(expected_exception, *args, **kwargs):
>>> with raises(ValueError) as exc_info: >>> with raises(ValueError) as exc_info:
... if value > 10: ... if value > 10:
... raise ValueError("value must be <= 10") ... raise ValueError("value must be <= 10")
... assert exc_info.type == ValueError # this will not execute ... assert exc_info.type is ValueError # this will not execute
Instead, the following approach must be taken (note the difference in Instead, the following approach must be taken (note the difference in
scope):: scope)::
@ -596,23 +614,10 @@ def raises(expected_exception, *args, **kwargs):
... if value > 10: ... if value > 10:
... raise ValueError("value must be <= 10") ... raise ValueError("value must be <= 10")
... ...
>>> assert exc_info.type == ValueError >>> assert exc_info.type is ValueError
Since version ``3.1`` you can use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
**Legacy form** **Legacy form**
The form below is fully supported but discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
It is possible to specify a callable by passing a to-be-called lambda:: It is possible to specify a callable by passing a to-be-called lambda::
>>> raises(ZeroDivisionError, lambda: 1/0) >>> raises(ZeroDivisionError, lambda: 1/0)
@ -627,9 +632,8 @@ def raises(expected_exception, *args, **kwargs):
>>> raises(ZeroDivisionError, f, x=0) >>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...> <ExceptionInfo ...>
.. currentmodule:: _pytest._code The form above is fully supported but discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
Consult the API of ``excinfo`` objects: :class:`ExceptionInfo`.
.. note:: .. note::
Similar to caught exception objects in Python, explicitly clearing Similar to caught exception objects in Python, explicitly clearing
@ -660,6 +664,7 @@ def raises(expected_exception, *args, **kwargs):
if not args: if not args:
if "message" in kwargs: if "message" in kwargs:
message = kwargs.pop("message") message = kwargs.pop("message")
warnings.warn(deprecated.RAISES_MESSAGE_PARAMETER, stacklevel=2)
if "match" in kwargs: if "match" in kwargs:
match_expr = kwargs.pop("match") match_expr = kwargs.pop("match")
if kwargs: if kwargs:
@ -668,7 +673,7 @@ def raises(expected_exception, *args, **kwargs):
raise TypeError(msg) raise TypeError(msg)
return RaisesContext(expected_exception, message, match_expr) return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str): elif isinstance(args[0], str):
warnings.warn(RAISES_EXEC, stacklevel=2) warnings.warn(deprecated.RAISES_EXEC, stacklevel=2)
code, = args code, = args
assert isinstance(code, str) assert isinstance(code, str)
frame = sys._getframe(1) frame = sys._getframe(1)

View File

@ -136,6 +136,12 @@ def test_pytest_catchlog_deprecated(testdir, plugin):
) )
def test_raises_message_argument_deprecated():
with pytest.warns(pytest.PytestDeprecationWarning):
with pytest.raises(RuntimeError, message="foobar"):
raise RuntimeError
def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir): def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST

View File

@ -121,8 +121,9 @@ class TestRaises(object):
def test_custom_raise_message(self): def test_custom_raise_message(self):
message = "TEST_MESSAGE" message = "TEST_MESSAGE"
try: try:
with pytest.raises(ValueError, message=message): with pytest.warns(PytestDeprecationWarning):
pass with pytest.raises(ValueError, message=message):
pass
except pytest.raises.Exception as e: except pytest.raises.Exception as e:
assert e.msg == message assert e.msg == message
else: else:

View File

@ -280,7 +280,7 @@ def test_assert_outcomes_after_pytest_error(testdir):
testdir.makepyfile("def test_foo(): assert True") testdir.makepyfile("def test_foo(): assert True")
result = testdir.runpytest("--unexpected-argument") result = testdir.runpytest("--unexpected-argument")
with pytest.raises(ValueError, message="Pytest terminal report not found"): with pytest.raises(ValueError, match="Pytest terminal report not found"):
result.assert_outcomes(passed=0) result.assert_outcomes(passed=0)