Doctests

Basic Examples

Including interactive examples in the documentation is helpful to users. They can be tested automatically and these tests are called doctests. We use pytest to run doctests, which in turn uses the Python’s doctest. Some examples:

def f():
    """Description.

    Examples
    --------
    >>> 13 * 2
    26

    """
    pass

and

.. rst file

>>> print("Hello ddg")
Hello ddg
>>> 13 * 2
26

or

.. rst file

.. doctest::

    >>> print("Hello ddg")
    Hello ddg
    >>> 13 * 2
    26

A multiline example:

.. doctest::

    >>> import ddg
    >>> cylinder = ddg.indexedfaceset.cylinder(
    ...     resolution=20,
    ...     top_radius=1,
    ...     bot_radius=1,
    ...     length=1,
    ...     center=(0, 0, 0),
    ...     normal=(0, 0, 1),
    ... )
    >>> cylinder
    <ddg.indexedfaceset._ifs.IndexedFaceSet object at 0x...>

For more details consult the doctest documentation.

Note

Avoid # doctest: +SKIP comments if at all possible. They used to be necessary in order to run the doctests outside of Blender without failures (as bpy isn’t available). This is no longer the case — nowadays Blender doctests are run within Blender.

Every Python docstring that has examples counts as one doctest. This means that one *.py file can have multiple doctests. In contrast, every *.rst file counts as one doctest.

Doctest Conventions

Use absolute paths when importing something in a doctest, don’t use * imports.

Do

.. doctest::

    >>> from ddg.math.grids import triangle_grid
    >>> triangle_grid
    <function triangle_grid at 0x...>

or

.. doctest::

    >>> import ddg
    >>> ddg.math.grids.triangle_grid
    <function triangle_grid at 0x...>

Do NOT

.. doctest::

    >>> from ddg.math.grids import *
    >>> triangle_grid
    <function triangle_grid at 0x...>

In general, it is best to show both the input and output like so

.. doctest::

    >>> import numpy as np
    >>> a = np.arange(10).reshape(2, 5)
    >>> a
    array([[0, 1, 2, 3, 4],
           [5, 6, 7, 8, 9]])
    >>> b = a * a
    >>> b
    array([[ 0,  1,  4,  9, 16],
           [25, 36, 49, 64, 81]])

This not only makes it easier to understand, but also asserts that the input a is what you expect it to be. If the input depends on the library and it changes in the future, then the doctest will fail which makes it easier to debug errors.

Warnings

Python’s doctest will fail if even a single character doesn’t match the expected output. Sometimes this is undesirable, because

  • output may not be deterministic, for example

    • repr and str may include memory addresses which change every time the program is executed,

    • repr and str may not guarantee any particular order for set and other unordered containers (note that dict is ordered as of Python 3.7),

  • output may be very long which decreases readability,

  • output may have tracebacks which show file paths that depend on where the program is stored.

See doctest’s documentation on directives and warnings on how to deal with these issues.