summaryrefslogtreecommitdiff
path: root/doc/en/doctest.rst
blob: 486868bb806c8c02ee8eecbd38388133b127fe87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311

Doctest integration for modules and test files
=========================================================

By default, all files matching the ``test*.txt`` pattern will
be run through the python standard ``doctest`` module.  You
can change the pattern by issuing:

.. code-block:: bash

    pytest --doctest-glob="*.rst"

on the command line. ``--doctest-glob`` can be given multiple times in the command-line.

If you then have a text file like this:

.. code-block:: text

    # content of test_example.txt

    hello this is a doctest
    >>> x = 3
    >>> x
    3

then you can just invoke ``pytest`` directly:

.. code-block:: pytest

    $ pytest
    =========================== test session starts ============================
    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
    cachedir: $PYTHON_PREFIX/.pytest_cache
    rootdir: $REGENDOC_TMPDIR
    collected 1 item

    test_example.txt .                                                   [100%]

    ============================ 1 passed in 0.12s =============================

By default, pytest will collect ``test*.txt`` files looking for doctest directives, but you
can pass additional globs using the ``--doctest-glob`` option (multi-allowed).

In addition to text files, you can also execute doctests directly from docstrings of your classes
and functions, including from test modules:

.. code-block:: python

    # content of mymodule.py
    def something():
        """a doctest in a docstring
        >>> something()
        42
        """
        return 42

.. code-block:: bash

    $ pytest --doctest-modules
    =========================== test session starts ============================
    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
    cachedir: $PYTHON_PREFIX/.pytest_cache
    rootdir: $REGENDOC_TMPDIR
    collected 2 items

    mymodule.py .                                                        [ 50%]
    test_example.txt .                                                   [100%]

    ============================ 2 passed in 0.12s =============================

You can make these changes permanent in your project by
putting them into a pytest.ini file like this:

.. code-block:: ini

    # content of pytest.ini
    [pytest]
    addopts = --doctest-modules


Encoding
--------

The default encoding is **UTF-8**, but you can specify the encoding
that will be used for those doctest files using the
``doctest_encoding`` ini option:

.. code-block:: ini

    # content of pytest.ini
    [pytest]
    doctest_encoding = latin1

Using 'doctest' options
-----------------------

Python's standard ``doctest`` module provides some `options <https://docs.python.org/3/library/doctest.html#option-flags>`__
to configure the strictness of doctest tests. In pytest, you can enable those flags using the
configuration file.

For example, to make pytest ignore trailing whitespaces and ignore
lengthy exception stack traces you can just write:

.. code-block:: ini

    [pytest]
    doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL

Alternatively, options can be enabled by an inline comment in the doc test
itself:

.. code-block:: rst

    >>> something_that_raises()  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ValueError: ...

pytest also introduces new options:

* ``ALLOW_UNICODE``: when enabled, the ``u`` prefix is stripped from unicode
  strings in expected doctest output. This allows doctests to run in Python 2
  and Python 3 unchanged.

* ``ALLOW_BYTES``: similarly, the ``b`` prefix is stripped from byte strings
  in expected doctest output.

* ``NUMBER``: when enabled, floating-point numbers only need to match as far as
  the precision you have written in the expected doctest output. For example,
  the following output would only need to match to 2 decimal places::

      >>> math.pi
      3.14

  If you wrote ``3.1416`` then the actual output would need to match to 4
  decimal places; and so on.

  This avoids false positives caused by limited floating-point precision, like
  this::

      Expected:
          0.233
      Got:
          0.23300000000000001

  ``NUMBER`` also supports lists of floating-point numbers -- in fact, it
  matches floating-point numbers appearing anywhere in the output, even inside
  a string! This means that it may not be appropriate to enable globally in
  ``doctest_optionflags`` in your configuration file.

  .. versionadded:: 5.1


Continue on failure
-------------------

By default, pytest would report only the first failure for a given doctest. If
you want to continue the test even when you have failures, do:

.. code-block:: bash

    pytest --doctest-modules --doctest-continue-on-failure


Output format
-------------

You can change the diff output format on failure for your doctests
by using one of standard doctest modules format in options
(see :data:`python:doctest.REPORT_UDIFF`, :data:`python:doctest.REPORT_CDIFF`,
:data:`python:doctest.REPORT_NDIFF`, :data:`python:doctest.REPORT_ONLY_FIRST_FAILURE`):

.. code-block:: bash

    pytest --doctest-modules --doctest-report none
    pytest --doctest-modules --doctest-report udiff
    pytest --doctest-modules --doctest-report cdiff
    pytest --doctest-modules --doctest-report ndiff
    pytest --doctest-modules --doctest-report only_first_failure


pytest-specific features
------------------------

Some features are provided to make writing doctests easier or with better integration with
your existing test suite. Keep in mind however that by using those features you will make
your doctests incompatible with the standard ``doctests`` module.

Using fixtures
^^^^^^^^^^^^^^

It is possible to use fixtures using the ``getfixture`` helper:

.. code-block:: text

    # content of example.rst
    >>> tmp = getfixture('tmpdir')
    >>> ...
    >>>

Note that the fixture needs to be defined in a place visible by pytest, for example, a `conftest.py`
file or plugin; normal python files containing docstrings are not normally scanned for fixtures
unless explicitly configured by :confval:`python_files`.

Also, the :ref:`usefixtures <usefixtures>` mark and fixtures marked as :ref:`autouse <autouse>` are supported
when executing text doctest files.


.. _`doctest_namespace`:

'doctest_namespace' fixture
^^^^^^^^^^^^^^^^^^^^^^^^^^^

The ``doctest_namespace`` fixture can be used to inject items into the
namespace in which your doctests run. It is intended to be used within
your own fixtures to provide the tests that use them with context.

``doctest_namespace`` is a standard ``dict`` object into which you
place the objects you want to appear in the doctest namespace:

.. code-block:: python

    # content of conftest.py
    import numpy


    @pytest.fixture(autouse=True)
    def add_np(doctest_namespace):
        doctest_namespace["np"] = numpy

which can then be used in your doctests directly:

.. code-block:: python

    # content of numpy.py
    def arange():
        """
        >>> a = np.arange(10)
        >>> len(a)
        10
        """
        pass

Note that like the normal ``conftest.py``, the fixtures are discovered in the directory tree conftest is in.
Meaning that if you put your doctest with your source code, the relevant conftest.py needs to be in the same directory tree.
Fixtures will not be discovered in a sibling directory tree!

Skipping tests
^^^^^^^^^^^^^^

For the same reasons one might want to skip normal tests, it is also possible to skip
tests inside doctests.

To skip a single check inside a doctest you can use the standard
`doctest.SKIP <https://docs.python.org/3/library/doctest.html#doctest.SKIP>`__ directive:

.. code-block:: python

    def test_random(y):
        """
        >>> random.random()  # doctest: +SKIP
        0.156231223

        >>> 1 + 1
        2
        """

This will skip the first check, but not the second.

pytest also allows using the standard pytest functions :func:`pytest.skip` and
:func:`pytest.xfail` inside doctests, which might be useful because you can
then skip/xfail tests based on external conditions:


.. code-block:: text

    >>> import sys, pytest
    >>> if sys.platform.startswith('win'):
    ...     pytest.skip('this doctest does not work on Windows')
    ...
    >>> import fcntl
    >>> ...

However using those functions is discouraged because it reduces the readability of the
docstring.

.. note::

    :func:`pytest.skip` and :func:`pytest.xfail` behave differently depending
    if the doctests are in a Python file (in docstrings) or a text file containing
    doctests intermingled with text:

    * Python modules (docstrings): the functions only act in that specific docstring,
      letting the other docstrings in the same module execute as normal.

    * Text files: the functions will skip/xfail the checks for the rest of the entire
      file.


Alternatives
------------

While the built-in pytest support provides a good set of functionalities for using
doctests, if you use them extensively you might be interested in those external packages
which add many more features, and include pytest integration:

* `pytest-doctestplus <https://github.com/astropy/pytest-doctestplus>`__: provides
  advanced doctest support and enables the testing of reStructuredText (".rst") files.

* `Sybil <https://sybil.readthedocs.io>`__: provides a way to test examples in
  your documentation by parsing them from the documentation source and evaluating
  the parsed examples as part of your normal test run.