如何运行 doctest

默认情况下,所有匹配 test*.txt 模式的文件都将通过 Python 标准的 doctest 模块运行。您可以通过在命令行中发出以下命令来更改模式:

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

在命令行中。--doctest-glob 可以在命令行中多次给定。

如果您有一个这样的文本文件

# content of test_example.txt

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

那么您可以直接调用 pytest

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_example.txt .                                                   [100%]

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

默认情况下,pytest 将收集 test*.txt 文件以查找 doctest 指令,但您可以使用 --doctest-glob 选项传递额外的 glob 模式(允许多次)。

除了文本文件,您还可以直接从类和函数的文档字符串中执行 doctest,包括从测试模块中执行。

# content of mymodule.py
def something():
    """a doctest in a docstring
    >>> something()
    42
    """
    return 42
$ pytest --doctest-modules
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items

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

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

您可以通过将这些更改放入 pytest.ini 文件中,使其在项目中永久生效,如下所示:

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

编码

默认编码是 UTF-8,但您可以使用 doctest_encoding ini 选项指定用于这些 doctest 文件的编码

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

使用 'doctest' 选项

Python 标准的 doctest 模块提供了一些选项来配置 doctest 测试的严格性。在 pytest 中,您可以使用配置文件启用这些标志。

例如,要让 pytest 忽略尾随空格和忽略冗长的异常堆栈跟踪,您只需编写

[pytest]
doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL

或者,可以通过 doctest 本身中的内联注释启用选项

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

pytest 还引入了新选项

  • ALLOW_UNICODE:启用时,预计的 doctest 输出中 unicode 字符串的 u 前缀将被剥离。这允许 doctest 在 Python 2 和 Python 3 中不变地运行。

  • ALLOW_BYTES:类似地,预计的 doctest 输出中字节字符串的 b 前缀将被剥离。

  • NUMBER:启用时,浮点数只需与您在预期 doctest 输出中写入的精度匹配即可。这些数字使用 pytest.approx() 进行比较,相对容差等于精度。例如,当将 3.14pytest.approx(math.pi, rel=10**-2) 进行比较时,以下输出只需匹配到小数点后 2 位:

    >>> math.pi
    3.14
    

    如果您编写了 3.1416,那么实际输出需要匹配到大约 4 位小数;依此类推。

    这可以避免由浮点精度有限引起的误报,例如这样

    Expected:
        0.233
    Got:
        0.23300000000000001
    

    NUMBER 还支持浮点数列表——事实上,它匹配输出中任意位置出现的浮点数,即使是在字符串内部!这意味着在您的配置文件中全局启用 doctest_optionflags 可能不合适。

    在版本 5.1 中添加。

失败时继续

默认情况下,pytest 只会报告给定 doctest 的第一个失败。如果您希望即使出现失败也继续测试,请执行以下操作:

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

输出格式

您可以通过在选项中使用标准 doctest 模块格式之一来更改 doctest 失败时的 diff 输出格式(参见 doctest.REPORT_UDIFFdoctest.REPORT_CDIFFdoctest.REPORT_NDIFFdoctest.REPORT_ONLY_FIRST_FAILURE

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 特有功能

提供了一些功能,以便更轻松地编写 doctest 或更好地与现有测试套件集成。但是请记住,使用这些功能会使您的 doctest 与标准 doctests 模块不兼容。

使用 fixtures

可以使用 getfixture 助手来使用 fixtures

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

请注意,fixture 需要定义在 pytest 可见的位置,例如 conftest.py 文件或插件中;包含文档字符串的普通 Python 文件通常不会被扫描以查找 fixture,除非由 python_files 明确配置。

此外,在执行文本 doctest 文件时,也支持 usefixtures 标记和标记为 autouse 的 fixture。

‘doctest_namespace’ fixture

doctest_namespace fixture 可用于将项注入到 doctest 运行的命名空间中。它旨在您自己的 fixture 中使用,以为使用它们的测试提供上下文。

doctest_namespace 是一个标准的 dict 对象,您将希望出现在 doctest 命名空间中的对象放入其中

# content of conftest.py
import pytest
import numpy


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

然后可以直接在您的 doctest 中使用

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

请注意,与普通的 conftest.py 一样,fixture 在 conftest 所在的目录树中被发现。这意味着如果您将 doctest 与源代码放在一起,相关的 conftest.py 需要位于相同的目录树中。Fixture 不会在兄弟目录树中被发现!

跳过测试

出于跳过普通测试的相同原因,也可以跳过 doctest 内部的测试。

要跳过 doctest 中的单个检查,您可以使用标准的 doctest.SKIP 指令

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

    >>> 1 + 1
    2
    """

这将跳过第一个检查,但不会跳过第二个。

pytest 还允许在 doctest 内部使用标准的 pytest 函数 pytest.skip()pytest.xfail(),这可能很有用,因为您可以根据外部条件跳过/xfail 测试

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

然而,不建议使用这些函数,因为它会降低文档字符串的可读性。

注意

pytest.skip()pytest.xfail() 的行为因 doctest 位于 Python 文件(在文档字符串中)还是包含文本的文本文件而异

  • Python 模块(文档字符串):这些函数只作用于特定的文档字符串,让同一模块中的其他文档字符串正常执行。

  • 文本文件:这些函数将跳过/xfail 文件中其余所有检查。

替代方案

虽然内置的 pytest 支持为使用 doctest 提供了良好的功能集,但如果您大量使用它们,您可能会对那些增加了更多功能并包含 pytest 集成的外部软件包感兴趣

  • pytest-doctestplus:提供高级 doctest 支持,并支持测试 reStructuredText(“.rst”)文件。

  • Sybil:提供了一种测试文档中示例的方法,通过从文档源解析它们并将解析后的示例作为正常测试运行的一部分进行评估。