297 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
			
		
		
	
	
			297 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
| .. _`warnings`:
 | |
| 
 | |
| Warnings Capture
 | |
| ================
 | |
| 
 | |
| .. versionadded:: 3.1
 | |
| 
 | |
| Starting from version ``3.1``, pytest now automatically catches warnings during test execution
 | |
| and displays them at the end of the session::
 | |
| 
 | |
|     # content of test_show_warnings.py
 | |
|     import warnings
 | |
| 
 | |
|     def api_v1():
 | |
|         warnings.warn(UserWarning("api v1, should use functions from v2"))
 | |
|         return 1
 | |
| 
 | |
|     def test_one():
 | |
|         assert api_v1() == 1
 | |
| 
 | |
| Running pytest now produces this output::
 | |
| 
 | |
|     $ pytest test_show_warnings.py
 | |
|     =========================== test session starts ============================
 | |
|     platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
 | |
|     rootdir: $REGENDOC_TMPDIR, inifile:
 | |
|     collected 1 item
 | |
|     
 | |
|     test_show_warnings.py .                                              [100%]
 | |
|     
 | |
|     ============================= warnings summary =============================
 | |
|     test_show_warnings.py::test_one
 | |
|       $REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
 | |
|         warnings.warn(UserWarning("api v1, should use functions from v2"))
 | |
|     
 | |
|     -- Docs: http://doc.pytest.org/en/latest/warnings.html
 | |
|     =================== 1 passed, 1 warnings in 0.12 seconds ===================
 | |
| 
 | |
| Pytest by default catches all warnings except for ``DeprecationWarning`` and ``PendingDeprecationWarning``.
 | |
| 
 | |
| The ``-W`` flag can be passed to control which warnings will be displayed or even turn
 | |
| them into errors::
 | |
| 
 | |
|     $ pytest -q test_show_warnings.py -W error::UserWarning
 | |
|     F                                                                    [100%]
 | |
|     ================================= FAILURES =================================
 | |
|     _________________________________ test_one _________________________________
 | |
|     
 | |
|         def test_one():
 | |
|     >       assert api_v1() == 1
 | |
|     
 | |
|     test_show_warnings.py:8: 
 | |
|     _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
 | |
|     
 | |
|         def api_v1():
 | |
|     >       warnings.warn(UserWarning("api v1, should use functions from v2"))
 | |
|     E       UserWarning: api v1, should use functions from v2
 | |
|     
 | |
|     test_show_warnings.py:4: UserWarning
 | |
|     1 failed in 0.12 seconds
 | |
| 
 | |
| The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option.
 | |
| For example, the configuration below will ignore all user warnings, but will transform
 | |
| all other warnings into errors.
 | |
| 
 | |
| .. code-block:: ini
 | |
| 
 | |
|     [pytest]
 | |
|     filterwarnings =
 | |
|         error
 | |
|         ignore::UserWarning
 | |
| 
 | |
| 
 | |
| When a warning matches more than one option in the list, the action for the last matching option
 | |
| is performed.
 | |
| 
 | |
| Both ``-W`` command-line option and ``filterwarnings`` ini option are based on Python's own
 | |
| `-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python
 | |
| documentation for other examples and advanced usage.
 | |
| 
 | |
| 
 | |
| .. _`filterwarnings`:
 | |
| 
 | |
| ``@pytest.mark.filterwarnings``
 | |
| -------------------------------
 | |
| 
 | |
| .. versionadded:: 3.2
 | |
| 
 | |
| You can use the ``@pytest.mark.filterwarnings`` to add warning filters to specific test items,
 | |
| allowing you to have finer control of which warnings should be captured at test, class or
 | |
| even module level:
 | |
| 
 | |
| .. code-block:: python
 | |
| 
 | |
|     import warnings
 | |
| 
 | |
|     def api_v1():
 | |
|         warnings.warn(UserWarning("api v1, should use functions from v2"))
 | |
|         return 1
 | |
| 
 | |
|     @pytest.mark.filterwarnings('ignore:api v1')
 | |
|     def test_one():
 | |
|         assert api_v1() == 1
 | |
| 
 | |
| 
 | |
| Filters applied using a mark take precedence over filters passed on the command line or configured
 | |
| by the ``filterwarnings`` ini option.
 | |
| 
 | |
| You may apply a filter to all tests of a class by using the ``filterwarnings`` mark as a class
 | |
| decorator or to all tests in a module by setting the ``pytestmark`` variable:
 | |
| 
 | |
| .. code-block:: python
 | |
| 
 | |
|     # turns all warnings into errors for this module
 | |
|     pytestmark = pytest.mark.filterwarnings('error')
 | |
| 
 | |
| 
 | |
| .. note::
 | |
| 
 | |
|     Except for these features, pytest does not change the python warning filter; it only captures
 | |
|     and displays the warnings which are issued with respect to the currently configured filter,
 | |
|     including changes to the filter made by test functions or by the system under test.
 | |
| 
 | |
| .. note::
 | |
| 
 | |
|     ``DeprecationWarning`` and ``PendingDeprecationWarning`` are hidden by the standard library
 | |
|     by default so you have to explicitly configure them to be displayed in your ``pytest.ini``:
 | |
| 
 | |
|     .. code-block:: ini
 | |
| 
 | |
|         [pytest]
 | |
|         filterwarnings =
 | |
|             once::DeprecationWarning
 | |
|             once::PendingDeprecationWarning
 | |
| 
 | |
| 
 | |
| *Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_
 | |
| *plugin.*
 | |
| 
 | |
| .. _`-W option`: https://docs.python.org/3/using/cmdline.html?highlight=#cmdoption-W
 | |
| .. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter
 | |
| .. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings
 | |
| 
 | |
| 
 | |
| Disabling warning capture
 | |
| -------------------------
 | |
| 
 | |
| This feature is enabled by default but can be disabled entirely in your ``pytest.ini`` file with:
 | |
| 
 | |
|     .. code-block:: ini
 | |
| 
 | |
|         [pytest]
 | |
|         addopts = -p no:warnings
 | |
| 
 | |
| Or passing ``-p no:warnings`` in the command-line.
 | |
| 
 | |
| .. _`asserting warnings`:
 | |
| 
 | |
| .. _assertwarnings:
 | |
| 
 | |
| .. _`asserting warnings with the warns function`:
 | |
| 
 | |
| .. _warns:
 | |
| 
 | |
| Asserting warnings with the warns function
 | |
| -----------------------------------------------
 | |
| 
 | |
| .. versionadded:: 2.8
 | |
| 
 | |
| You can check that code raises a particular warning using ``pytest.warns``,
 | |
| which works in a similar manner to :ref:`raises <assertraises>`::
 | |
| 
 | |
|     import warnings
 | |
|     import pytest
 | |
| 
 | |
|     def test_warning():
 | |
|         with pytest.warns(UserWarning):
 | |
|             warnings.warn("my warning", UserWarning)
 | |
| 
 | |
| The test will fail if the warning in question is not raised. The keyword
 | |
| argument ``match`` to assert that the exception matches a text or regex::
 | |
| 
 | |
|     >>> with warns(UserWarning, match='must be 0 or None'):
 | |
|     ...     warnings.warn("value must be 0 or None", UserWarning)
 | |
| 
 | |
|     >>> with warns(UserWarning, match=r'must be \d+$'):
 | |
|     ...     warnings.warn("value must be 42", UserWarning)
 | |
| 
 | |
|     >>> with warns(UserWarning, match=r'must be \d+$'):
 | |
|     ...     warnings.warn("this is not here", UserWarning)
 | |
|     Traceback (most recent call last):
 | |
|       ...
 | |
|     Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
 | |
| 
 | |
| You can also call ``pytest.warns`` on a function or code string::
 | |
| 
 | |
|     pytest.warns(expected_warning, func, *args, **kwargs)
 | |
|     pytest.warns(expected_warning, "func(*args, **kwargs)")
 | |
| 
 | |
| The function also returns a list of all raised warnings (as
 | |
| ``warnings.WarningMessage`` objects), which you can query for
 | |
| additional information::
 | |
| 
 | |
|     with pytest.warns(RuntimeWarning) as record:
 | |
|         warnings.warn("another warning", RuntimeWarning)
 | |
| 
 | |
|     # check that only one warning was raised
 | |
|     assert len(record) == 1
 | |
|     # check that the message matches
 | |
|     assert record[0].message.args[0] == "another warning"
 | |
| 
 | |
| Alternatively, you can examine raised warnings in detail using the
 | |
| :ref:`recwarn <recwarn>` fixture (see below).
 | |
| 
 | |
| .. note::
 | |
|     ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
 | |
|     differently; see :ref:`ensuring_function_triggers`.
 | |
| 
 | |
| .. _`recording warnings`:
 | |
| 
 | |
| .. _recwarn:
 | |
| 
 | |
| Recording warnings
 | |
| ------------------------
 | |
| 
 | |
| You can record raised warnings either using ``pytest.warns`` or with
 | |
| the ``recwarn`` fixture.
 | |
| 
 | |
| To record with ``pytest.warns`` without asserting anything about the warnings,
 | |
| pass ``None`` as the expected warning type::
 | |
| 
 | |
|     with pytest.warns(None) as record:
 | |
|         warnings.warn("user", UserWarning)
 | |
|         warnings.warn("runtime", RuntimeWarning)
 | |
| 
 | |
|     assert len(record) == 2
 | |
|     assert str(record[0].message) == "user"
 | |
|     assert str(record[1].message) == "runtime"
 | |
| 
 | |
| The ``recwarn`` fixture will record warnings for the whole function::
 | |
| 
 | |
|     import warnings
 | |
| 
 | |
|     def test_hello(recwarn):
 | |
|         warnings.warn("hello", UserWarning)
 | |
|         assert len(recwarn) == 1
 | |
|         w = recwarn.pop(UserWarning)
 | |
|         assert issubclass(w.category, UserWarning)
 | |
|         assert str(w.message) == "hello"
 | |
|         assert w.filename
 | |
|         assert w.lineno
 | |
| 
 | |
| Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded
 | |
| warnings: a WarningsRecorder instance. To view the recorded warnings, you can
 | |
| iterate over this instance, call ``len`` on it to get the number of recorded
 | |
| warnings, or index into it to get a particular recorded warning.
 | |
| 
 | |
| .. currentmodule:: _pytest.warnings
 | |
| 
 | |
| Full API: :class:`WarningsRecorder`.
 | |
| 
 | |
| .. _`ensuring a function triggers a deprecation warning`:
 | |
| 
 | |
| .. _ensuring_function_triggers:
 | |
| 
 | |
| Ensuring a function triggers a deprecation warning
 | |
| -------------------------------------------------------
 | |
| 
 | |
| You can also call a global helper for checking
 | |
| that a certain function call triggers a ``DeprecationWarning`` or
 | |
| ``PendingDeprecationWarning``::
 | |
| 
 | |
|     import pytest
 | |
| 
 | |
|     def test_global():
 | |
|         pytest.deprecated_call(myfunction, 17)
 | |
| 
 | |
| By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
 | |
| caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
 | |
| them. If you wish to record them in your own code, use the
 | |
| command ``warnings.simplefilter('always')``::
 | |
| 
 | |
|     import warnings
 | |
|     import pytest
 | |
| 
 | |
|     def test_deprecation(recwarn):
 | |
|         warnings.simplefilter('always')
 | |
|         warnings.warn("deprecated", DeprecationWarning)
 | |
|         assert len(recwarn) == 1
 | |
|         assert recwarn.pop(DeprecationWarning)
 | |
| 
 | |
| You can also use it as a contextmanager::
 | |
| 
 | |
|     def test_global():
 | |
|         with pytest.deprecated_call():
 | |
|             myobject.deprecated_method()
 |