文档测试

Python 文档测试(doctest)教程

什么是文档测试?

文档测试(doctest)是 Python 的一个内置模块,它允许你在文档字符串(docstring)中嵌入可执行的测试用例。这些测试用例看起来就像 Python 交互式解释器中的输入和预期输出。

为什么使用文档测试?

  1. 自文档化代码:测试用例本身就是最好的文档示例
  2. 确保文档准确性:文档中的示例代码会被实际执行验证
  3. 简单易用:不需要额外的测试框架
  4. 与文档生成工具集成:如 Sphinx 可以自动提取这些示例

基本用法

1. 在函数文档中编写测试

def abs(n):
    """
    返回数字的绝对值
    
    示例:
    
    >>> abs(1)
    1
    >>> abs(-1)
    1
    >>> abs(0)
    0
    """
    return n if n >= 0 else (-n)

2. 在类文档中编写测试

class Dict(dict):
    """
    支持属性访问方式的字典
    
    示例:
    
    >>> d = Dict(a=1, b=2)
    >>> d.a
    1
    >>> d['b']
    2
    >>> d.c = 3
    >>> d['c']
    3
    >>> d['nonexistent']
    Traceback (most recent call last):
    ...
    KeyError: 'nonexistent'
    """
    
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(f"'Dict' object has no attribute '{key}'")
    
    def __setattr__(self, key, value):
        self[key] = value

3. 运行文档测试

在模块底部添加:

if __name__ == '__main__':
    import doctest
    doctest.testmod()

然后通过命令行运行:

python your_module.py

如果没有输出,表示所有测试通过;如果有失败,会显示详细的错误信息。

高级特性

1. 处理多行输出

对于多行输出,可以使用 ... 表示中间省略的部分:

def split_lines(text):
    """
    分割文本行
    
    示例:
    
    >>> split_lines('line1\\nline2\\nline3')
    ['line1', 'line2', 'line3']
    >>> print('a\\nb\\nc')
    a
    b
    c
    >>> import sys
    >>> sys.version_info  # doctest: +ELLIPSIS
    sys.version_info(major=3, ...)
    """
    return text.splitlines()

2. 测试异常

测试异常时,只需要包含异常的第一行和最后一行:

def divide(a, b):
    """
    除法运算
    
    示例:
    
    >>> divide(10, 2)
    5.0
    >>> divide(1, 0)
    Traceback (most recent call last):
    ...
    ZeroDivisionError: division by zero
    """
    return a / b

3. 跳过不可预测的输出

对于每次运行可能不同的输出(如内存地址),可以使用 # doctest: +SKIP

def get_object():
    """
    返回新对象
    
    示例:
    
    >>> get_object()  # doctest: +SKIP
    <object object at 0x...>
    """
    return object()

4. 正则表达式匹配

对于需要更灵活的输出匹配,可以使用 # doctest: +ELLIPSIS

import datetime

def current_year():
    """
    返回当前年份
    
    示例:
    
    >>> 1900 <= current_year() <= 2100  # doctest: +ELLIPSIS
    True
    """
    return datetime.date.today().year

实际应用示例

阶乘函数测试

def factorial(n):
    """
    计算阶乘
    
    示例:
    
    >>> factorial(1)
    1
    >>> factorial(5)
    120
    >>> factorial(0)
    Traceback (most recent call last):
    ...
    ValueError: n must be >= 1
    >>> factorial(-1)
    Traceback (most recent call last):
    ...
    ValueError: n must be >= 1
    >>> factorial(10)
    3628800
    """
    if n < 1:
        raise ValueError("n must be >= 1")
    if n == 1:
        return 1
    return n * factorial(n - 1)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

最佳实践

  1. 保持测试简单:每个测试用例应该只测试一个方面
  2. 测试边界条件:包括正常值、边界值和非法值
  3. 测试异常情况:确保错误被正确处理
  4. 避免副作用:测试不应该改变程序状态
  5. 与单元测试结合:doctest 适合简单测试,复杂测试应该用 unittest 或 pytest

与文档工具集成

流行的文档工具如 Sphinx 可以自动提取 doctest 示例到生成的文档中。配置 Sphinx 时,启用 doctest 扩展:

# conf.py
extensions = [
    'sphinx.ext.doctest',
    # 其他扩展...
]

然后运行:

make doctest

总结

Python 的 doctest 模块提供了一种简单有效的方式来:

  • 验证代码示例的正确性
  • 保持文档与代码同步
  • 作为轻量级的测试框架

通过将测试用例直接嵌入文档字符串,你可以创建自文档化、可验证的代码,这是 Python 生态系统中的一个强大工具。