如何运行 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-9.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 选项(允许多个)传递额外的全局模式。
除了文本文件,您还可以直接从类和函数的 docstring 中执行 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-9.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 =============================
您可以通过将这些更改放入这样的配置文件中,使其在您的项目中永久生效
# content of pytest.toml
[pytest]
addopts = ["--doctest-modules"]
编码¶
默认编码是 UTF-8,但您可以使用 doctest_encoding 配置选项指定用于这些 doctest 文件的编码
[pytest]
doctest_encoding = "latin1"
[pytest]
doctest_encoding = latin1
使用“doctest”选项¶
Python 的标准 doctest 模块提供了一些 选项 来配置 doctest 测试的严格性。在 pytest 中,您可以通过配置文件启用这些标志。
例如,要使 pytest 忽略尾随空格并忽略冗长的异常堆栈跟踪,您只需编写
[pytest]
doctest_optionflags = ["NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"]
[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:启用时,unicode 字符串的u前缀在预期的 doctest 输出中被删除。这允许 doctest 在 Python 2 和 Python 3 中不变地运行。ALLOW_BYTES:类似地,字节字符串的b前缀在预期的 doctest 输出中被删除。NUMBER:启用时,浮点数只需与您在预期 doctest 输出中写入的精度匹配。数字使用pytest.approx()进行比较,相对容差等于精度。例如,当将3.14与pytest.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_UDIFF、doctest.REPORT_CDIFF、doctest.REPORT_NDIFF、doctest.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 模块不兼容。
使用 fixture¶
可以使用 getfixture 助手来使用 fixture
# content of example.rst
>>> tmp = getfixture('tmp_path')
>>> ...
>>>
请注意,fixture 需要定义在 pytest 可见的位置,例如 conftest.py 文件或插件;除非由 python_files 明确配置,否则通常不会扫描包含 docstring 的普通 python 文件以查找 fixture。
此外,在执行文本 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(),这可能很有用,因为您可以根据外部条件跳过/预期失败测试
>>> import sys, pytest
>>> if sys.platform.startswith('win'):
... pytest.skip('this doctest does not work on Windows')
...
>>> import fcntl
>>> ...
但是不鼓励使用这些函数,因为它会降低 docstring 的可读性。
注意
pytest.skip() 和 pytest.xfail() 的行为不同,具体取决于 doctest 是在 Python 文件中(在 docstring 中)还是在包含 doctest 与文本混合的文本文件中
Python 模块(docstring):这些函数只作用于特定的 docstring,让同一模块中的其他 docstring 正常执行。
文本文件:这些函数将跳过/预期失败整个文件中其余的检查。
替代方案¶
虽然内置的 pytest 支持为使用 doctest 提供了一套良好的功能,但如果您广泛使用它们,您可能会对那些添加更多功能并包含 pytest 集成的外部包感兴趣
pytest-doctestplus:提供高级 doctest 支持,并支持测试 reStructuredText (“.rst”) 文件。
Sybil:提供了一种测试文档中示例的方法,它通过解析文档源并评估解析后的示例作为正常测试运行的一部分来完成。