如何用 pytest 运行 doctest
默认情况下,pytest 会使用 Python 标准库的 doctest 模块,运行所有匹配 test*.txt 的文件。可以在命令行更改匹配模式:
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,加入额外的 glob 模式。
除了文本文件,使用 --doctest-modules 还可以直接执行类、函数文档字符串里的 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 文件采用的编码。
TOML:
[pytest]
doctest_encoding = "latin1"
INI:
[pytest]
doctest_encoding = latin1
使用 doctest 选项
Python 标准库的 doctest 选项控制测试匹配的严格程度。在 pytest 中,可以通过配置文件启用这些标志。
例如,要归一化空白并忽略异常细节,可以这样配置。
TOML:
[pytest]
doctest_optionflags = ["NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"]
INI:
[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 可以用于 Python 2 和 Python 3。ALLOW_BYTES:类似地去除字节串的b前缀。NUMBER:浮点数只需匹配到预期输出写出的精度。比较使用pytest.approx(),相对容差由写出的精度决定。例如:
>>> math.pi
3.14
这里比较 3.14 与 pytest.approx(math.pi, rel=10**-2),只要求约两位小数的精度。若预期输出写成 3.1416,则要求约四位小数,以此类推。
这样可以避免有限浮点精度导致的错误失败,例如:
Expected:
0.233
Got:
0.23300000000000001
NUMBER 也支持浮点数列表。实际上,它会匹配输出任意位置的浮点数,包括字符串内部。因此,并不适合在所有项目中通过 doctest_optionflags 全局启用。
该选项在 pytest 5.1 中加入。
失败后继续
默认情况下,pytest 对一个 doctest 只报告首个失败。若要在出现失败后继续执行:
pytest --doctest-modules --doctest-continue-on-failure
输出格式
可以使用标准 doctest 模块支持的格式,更改失败时的差异输出。参见 REPORT_UDIFF、REPORT_CDIFF、REPORT_NDIFF、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 将不再与标准库 doctest 模块兼容。
使用 fixture
可以通过 getfixture 辅助函数使用 fixture:
# content of example.rst
>>> tmp = getfixture('tmp_path')
>>> ...
>>>
fixture 必须定义在 pytest 能发现的位置,例如 conftest.py 或插件。普通的、包含文档字符串的 Python 文件通常不会被扫描以发现 fixture,除非通过 python_files 显式配置。
运行文本 doctest 文件时,也支持 usefixtures 标记和 autouse fixture。
Python doctest 模块与 Python 测试文件独立收集。两者不共享 fixture 作用域。
doctest 不支持依赖参数化的 fixture,因为 doctest 收集过程不会像普通测试函数那样生成测试。这也包括参数化的 autouse fixture。若需要针对多个后端或配置运行 doctest,可以考虑把检查移到普通测试函数,或使用专门的 doctest 插件。
doctest_namespace fixture
doctest_namespace 用于向 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 按其所在目录树发现。如果 doctest 放在源代码目录里,相应 conftest.py 也必须位于同一目录树。位于相邻目录树中的 fixture 不会被发现。
跳过测试
与普通测试一样,也可能需要跳过 doctest 中的检查。
要跳过单次检查,可以使用标准指令 doctest.SKIP:
def test_random(y):
"""
>>> random.random() # doctest: +SKIP
0.156231223
>>> 1 + 1
2
"""
这里只跳过第一项检查,第二项仍会执行。
pytest 还允许在 doctest 中调用 pytest.skip() 与 pytest.xfail()。这便于根据外部条件跳过测试或标记预期失败:
>>> import sys, pytest
>>> if sys.platform.startswith('win'):
... pytest.skip('this doctest does not work on Windows')
...
>>> import fcntl
>>> ...
不过,原文不建议这样做,因为它会降低文档字符串的可读性。
这些函数的作用范围取决于 doctest 所在文件:
- Python 模块中的文档字符串:只影响当前文档字符串,同一模块中的其他文档字符串仍正常执行。
- 混有正文和 doctest 的文本文件:跳过或标记预期失败的范围覆盖文件中剩余的全部检查。
替代方案
pytest 的内置支持提供了较完整的 doctest 功能。如果大量使用 doctest,也可以考虑以下提供更多功能并集成 pytest 的外部项目:
- pytest-doctestplus:提供进阶 doctest 支持,并支持测试 reStructuredText(
.rst)文件。 - Sybil:从文档源中解析示例,再将这些示例作为常规测试运行的一部分进行求值。
原文:How to run doctests。Copyright © 2015, holger krekel and pytest-dev team。中文翻译;pytest 及相关文档遵循 MIT 许可证。
The MIT License (MIT)
Copyright (c) 2004 Holger Krekel and others
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.











暂无评论内容