用 pytest --doctest-modules 让文档示例参与测试

举报
yd_229231784 发表于 2026/11/10 10:02:45 2026/11/10
【摘要】 介绍用 pytest 的 --doctest-modules 收集函数文档字符串示例,在 pytest.ini 中开启 doctest、配置 ELLIPSIS 与目录忽略,并给出可运行示例和边界说明。

用 pytest --doctest-modules 让文档示例参与测试

问题场景

函数文档字符串里的示例代码,常常在第一次编写时是正确的;后续修改返回值、字段名或输出格式时,示例没有同步更新,文档就变成过时示例。读者复制 >>> 示例运行后得到不同结果,排查成本很高。把文档示例纳入自动化测试,可以让这类问题在提交前暴露。

解决思路

pytest 内置 doctest 插件,可以收集模块、类、函数文档字符串中的交互式示例,并按测试执行。开启 --doctest-modules 后,pytest 会导入目标模块并收集其中的 doctest。再通过 pytest.ini 配置 doctest_optionflags,用 ELLIPSIS 处理随机 id、时间戳等动态片段,用 NORMALIZE_WHITESPACE 减少空白差异导致的误报。对于第三方目录、构建产物目录,用 --ignore 排除,避免 pytest 导入无关模块。

最小可运行示例

目录结构如下,示例值可按实际项目替换:

project/
├── pytest.ini
└── src/
    └── mathlib.py

src/mathlib.py:

'''小工具模块:文档字符串中的示例同时作为 doctest 执行。'''

from uuid import uuid4


def add(a, b):
    '''返回两个整数之和。

    >>> add(2, 3)
    5
    >>> add(-1, 1)
    0
    '''
    return a + b


def build_payload(name):
    '''构造带随机 id 的载荷。

    >>> build_payload('demo')  # doctest: +ELLIPSIS
    {'id': '...', 'name': 'demo'}
    '''
    return {'id': uuid4().hex, 'name': name}


def split_tags(raw):
    '''拆分逗号分隔的标签,并忽略空白项。

    >>> split_tags('a, b,,c ')
    ['a', 'b', 'c']
    >>> split_tags('')
    []
    '''
    return [item.strip() for item in raw.split(',') if item.strip()]

这里 build_payload 的 id 每次不同,所以示例用 ... 匹配动态部分。代码中保留了 # doctest: +ELLIPSIS 指令,便于单独运行 doctest 时也能通过;如果已在 pytest.ini 全局开启 ELLIPSIS,该行可以省略。

pytest.ini:

[pytest]
testpaths = src
addopts = --doctest-modules --ignore=third_party --ignore=build
doctest_optionflags = ELLIPSIS NORMALIZE_WHITESPACE

testpaths 限定只从 src 收集,addopts 让每次运行 pytest 都开启模块 doctest 收集,--ignore 排除第三方和构建目录。doctest_optionflags 同时开启省略号匹配与空白归一化。

运行与验证

在项目根目录执行:

python -m pytest -q

也可以只验证单个模块:

python -m pytest src/mathlib.py -q

或者显式传入参数,便于在脚本中复现:

python -m pytest --doctest-modules src/mathlib.py -q

若想看到每个 doctest 的收集情况,可加 -v。验证测试有效性时,可临时把 add 的实现改成 return a - b,再运行 pytest,add(2, 3) 示例会因期望 5、实际 -1 而失败;确认后恢复实现。

边界与限制

  1. doctest 比较的是输出字符串,不是对象语义。字典通常按插入顺序展示,集合顺序不稳定,建议在示例中使用 sorted(...) 或只展示稳定字段。
  2. 浮点数要避免直接比较完整小数。可在示例中先 round(value, 2),或使用固定输入与可预测输出。
  3. 异常示例需要写出 Traceback (most recent call last): 与末尾异常行,中间细节可用 ... 匹配;不要只写异常类型而省略 Traceback 头。
  4. ELLIPSIS 很方便,但省略范围过大可能掩盖错误。动态 id、时间戳可以省略,业务字段、关键数值应保持明确。
  5. --doctest-modules 会导入 testpaths 下的 Python 模块,模块顶层如果执行网络请求、数据库连接、读取本地文件或启动服务,会在收集阶段产生副作用。此类模块应拆分出纯函数,或用 --ignore 排除。
  6. doctest 不适合替代完整单元测试。它没有 pytest fixture、参数化和 monkeypatch 等能力;复杂依赖注入、临时目录、异常分支,仍应写在普通测试函数中。
  7. # doctest: +SKIP 可以临时跳过示例,但应说明原因并定期清理,避免文档示例长期不验证。
  8. 在 CI 中使用固定命令 python -m pytest -q,确保本地与流水线都读取同一份 pytest.ini,减少配置差异。

按以上方式,文档示例会随着 pytest 运行被持续验证,函数行为变更时能及时暴露过时文档。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。