doctest --- 测试交互式 Python 示例

源代码: Lib/doctest.py


doctest 模块会搜索看起来像交互式 Python 会话的文本片段,然后执行这些会话,以验证它们是否完全按照所示的方式工作。有几种常见的方式来使用 doctest:

  • 通过验证所有交互式示例仍然按文档所述工作,来检查模块的文档字符串是否最新。

  • 通过验证测试文件或测试对象中的交互式示例是否按预期工作,来执行回归测试。

  • 为一个包编写教程文档,并大量使用输入输出示例进行说明。根据重点是示例还是说明性文本,这具有“文学式测试”或“可执行文档”的风格。

这是一个完整但很小的示例模块:

"""
This is the "example" module.

The example module supplies one function, factorial().  For example,

>>> factorial(5)
120
"""

def factorial(n):
    """Return the factorial of n, an exact integer >= 0.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(30)
    265252859812191058636308480000000
    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0

    Factorials of floats are OK, but the float must be an exact integer:
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
    >>> factorial(30.0)
    265252859812191058636308480000000

    It must also not be ridiculously large:
    >>> factorial(1e100)
    Traceback (most recent call last):
        ...
    OverflowError: n too large
    """

    import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result


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

如果你直接从命令行运行 example.pydoctest 就会发挥它的魔力:

$ python example.py
$

没有任何输出!这很正常,意味着所有示例都通过了。向脚本传递 -vdoctest 会打印一份它尝试执行的详细日志,并在末尾打印一个摘要:

$ python example.py -v
Trying:
    factorial(5)
Expecting:
    120
ok
Trying:
    [factorial(n) for n in range(6)]
Expecting:
    [1, 1, 2, 6, 24, 120]
ok

等等,最终以以下内容结束:

Trying:
    factorial(1e100)
Expecting:
    Traceback (most recent call last):
        ...
    OverflowError: n too large
ok
2 items passed all tests:
   1 test in __main__
   6 tests in __main__.factorial
7 tests in 2 items.
7 passed.
Test passed.
$

这就是开始高效使用 doctest 所需了解的全部内容!开始使用吧。以下各节提供了完整的详细信息。请注意,标准 Python 测试套件和库中有许多 doctest 的例子。特别有用的例子可以在标准测试文件 Lib/test/test_doctest/test_doctest.py 中找到。

在 3.13 版本加入: 输出默认是彩色的,并且可以通过环境变量进行控制

简单用法:检查文档字符串中的示例

开始使用 doctest 最简单的方法(但不一定是你将继续使用的方式)是在每个模块 M 的末尾加上:

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

然后,doctest 会检查模块 M 中的文档字符串。

将模块作为脚本运行,会导致文档字符串中的示例被执行和验证:

python M.py

除非有示例失败,否则这不会显示任何内容。如果示例失败,失败的示例及其原因将被打印到标准输出,输出的最后一行是 ***Test Failed*** N failures.,其中 N 是失败示例的数量。

改用 -v 开关运行它:

python M.py -v

然后,所有尝试的示例的详细报告将被打印到标准输出,并在末尾附有各种摘要。

你可以通过向 testmod() 传递 verbose=True 来强制进入详细模式,或者通过传递 verbose=False 来禁止它。在这两种情况下,sys.argv 都不会被 testmod() 检查(所以传递 -v 或不传递都没有效果)。

还有一个用于运行 testmod() 的命令行快捷方式,请参见 命令行用法 部分。

关于 testmod() 的更多信息,请参见 基本 API 部分。

简单用法:检查文本文件中的示例

doctest 的另一个简单应用是测试文本文件中的交互式示例。这可以通过 testfile() 函数完成:

import doctest
doctest.testfile("example.txt")

这段简短的脚本会执行并验证文件 example.txt 中包含的任何交互式 Python 示例。文件内容被视为一个巨大的文档字符串;该文件不需要包含 Python 程序!例如,也许 example.txt 包含以下内容:

The ``example`` module
======================

Using ``factorial``
-------------------

This is an example text file in reStructuredText format.  First import
``factorial`` from the ``example`` module:

    >>> from example import factorial

Now use it:

    >>> factorial(6)
    120

运行 doctest.testfile("example.txt") 会发现此文档中的错误:

File "./example.txt", line 14, in example.txt
Failed example:
    factorial(6)
Expected:
    120
Got:
    720

testmod() 一样,testfile() 除非有示例失败,否则不会显示任何内容。如果示例失败,失败的示例及其原因将使用与 testmod() 相同的格式打印到标准输出。

默认情况下,testfile() 会在调用模块的目录中查找文件。有关可用于让它在其他位置查找文件的可选参数的描述,请参见 基本 API 部分。

testmod() 一样,testfile() 的详细程度可以通过 -v 命令行开关或可选的关键字参数 *verbose* 来设置。

还有一个用于运行 testfile() 的命令行快捷方式,请参见 命令行用法 部分。

关于 testfile() 的更多信息,请参见 基本 API 部分。

命令行用法

doctest 模块可以从命令行作为脚本调用:

python -m doctest [-v] [-o OPTION] [-f] file [file ...]
-v, --verbose

所有尝试过的示例的详细报告会打印到标准输出,并在末尾附上各种摘要:

python -m doctest -v example.py

这将把 example.py 作为一个独立的模块导入,并对其运行 testmod()。请注意,如果文件是包的一部分并且从该包导入其他子模块,这可能无法正常工作。

如果文件名不以 .py 结尾,doctest 会推断它必须使用 testfile() 运行:

python -m doctest -v example.txt
-o, --option <option>

选项标志控制 doctest 行为的各个方面,请参见 选项标志 部分。

在 3.4 版本加入。

-f, --fail-fast

这是 -o FAIL_FAST 的简写。

在 3.4 版本加入。

工作原理

本节详细阐述了 doctest 的工作原理:它检查哪些文档字符串,如何找到交互式示例,使用什么执行上下文,如何处理异常,以及如何使用选项标志来控制其行为。这些是编写 doctest 示例需要知道的信息;关于如何在这些示例上实际运行 doctest 的信息,请参见以下各节。

检查哪些文档字符串?

模块文档字符串以及所有函数、类和方法的文档字符串都会被搜索。导入到模块中的对象不会被搜索。

此外,在某些情况下,你希望测试成为模块的一部分,但不属于帮助文本,这就要求测试不包含在文档字符串中。Doctest 会查找一个名为 __test__ 的模块级变量,并用它来定位其他测试。如果 M.__test__ 存在,它必须是一个字典,每个条目将一个(字符串)名称映射到一个函数对象、类对象或字符串。从 M.__test__ 中找到的函数和类对象的文档字符串会被搜索,而字符串则被视为文档字符串。在输出中,M.__test__ 中的键 K 将以 M.__test__.K 的名称出现。

例如,将以下代码块放在 example.py 的顶部:

__test__ = {
    'numbers': """
>>> factorial(6)
720

>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
"""
}

example.__test__["numbers"] 的值将被视为一个文档字符串,其中所有的测试都将被运行。需要注意的是,该值可以映射到一个函数、类对象或模块;如果是这样,doctest 会递归地搜索它们的文档字符串,然后扫描这些文档字符串以查找测试。

任何找到的类都会被类似地递归搜索,以测试其包含的方法和嵌套类中的文档字符串。

备注

doctest 只能自动发现在模块级别或其他类内部定义的类和函数。

由于嵌套的类和函数仅在外部函数被调用时才存在,因此无法被发现。请在外部定义它们以使其可见。

如何识别文档字符串示例?

在大多数情况下,复制粘贴交互式控制台会话的内容效果很好,但 doctest 并不试图精确模拟任何特定的 Python shell。

>>> # comments are ignored
>>> x = 12
>>> x
12
>>> if x == 13:
...     print("yes")
... else:
...     print("no")
...     print("NO")
...     print("NO!!!")
...
no
NO
NO!!!
>>>

任何预期的输出都必须紧跟在包含代码的最后一行 '>>> ''... ' 之后,并且预期的输出(如果有的话)会延伸到下一个 '>>> ' 或全为空白字符的行。

详细说明:

  • 预期输出不能包含一个全为空白字符的行,因为这样的行被视为预期输出结束的信号。如果预期输出确实包含空行,请在你的 doctest 示例中每个预期空行的地方放置 <BLANKLINE>

  • 所有硬制表符字符都会被扩展为空格,使用 8 列制表位。被测试代码生成的输出中的制表符不会被修改。因为示例输出中的任何硬制表符*都*会被扩展,这意味着如果代码输出包含硬制表符,doctest 能够通过的唯一方法是 NORMALIZE_WHITESPACE 选项或 指令 生效。或者,可以重写测试以捕获输出,并将其与预期值进行比较作为测试的一部分。这种对源代码中制表符的处理是通过反复试验得出的,并被证明是处理它们时最不容易出错的方式。可以通过编写自定义的 DocTestParser 类来使用不同的算法处理制表符。

  • 标准输出 (stdout) 会被捕获,但标准错误 (stderr) 的输出不会(异常回溯通过另一种方式捕获)。

  • 如果你在交互式会话中通过反斜杠续行,或因任何其他原因使用反斜杠,你应该使用原始文档字符串,它会完全按照你输入的方式保留你的反斜杠:

    >>> def f(x):
    ...     r'''Backslashes in a raw docstring: m\n'''
    ...
    >>> print(f.__doc__)
    Backslashes in a raw docstring: m\n
    

    否则,反斜杠将被解释为字符串的一部分。例如,上面的 \n 将被解释为一个换行符。或者,你可以在 doctest 版本中将每个反斜杠加倍(并且不使用原始字符串):

    >>> def f(x):
    ...     '''Backslashes in a raw docstring: m\\n'''
    ...
    >>> print(f.__doc__)
    Backslashes in a raw docstring: m\n
    
  • 起始列无关紧要:

    >>> assert "Easy!"
          >>> import math
              >>> math.floor(1.9)
              1
    

    并且从预期输出中剥离的开头空白字符数量与开始示例的初始 '>>> ' 行中的数量相同。

执行上下文是什么?

默认情况下,每次 doctest 找到一个要测试的文档字符串时,它会使用 M 的全局变量的*浅拷贝*,这样运行测试就不会改变模块的实际全局变量,并且 M 中的一个测试不会留下意外让另一个测试通过的“碎屑”。这意味着示例可以自由使用在 M 的顶层定义的任何名称,以及在正在运行的文档字符串中较早定义的名称。示例看不到在其他文档字符串中定义的名称。

你可以通过向 testmod()testfile() 传递 globs=your_dict 来强制使用你自己的字典作为执行上下文。

异常怎么办?

没问题,只要回溯信息是示例产生的唯一输出:只需将回溯信息粘贴进去即可。[1] 由于回溯信息包含可能迅速变化的细节(例如,确切的文件路径和行号),doctest 在这种情况下会尽力灵活地接受输入。

简单示例:

>>> [1, 2, 3].remove(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list

如果引发了 ValueError,并且详细信息为 list.remove(x): x not in list,那么该 doctest 将会成功。

异常的预期输出必须以回溯头开始,可以是以下两行中的任意一行,缩进与示例的第一行相同:

Traceback (most recent call last):
Traceback (innermost last):

回溯头后面是可选的回溯堆栈,其内容被 doctest 忽略。回溯堆栈通常被省略,或者直接从交互式会话中复制。

回溯堆栈之后是最有趣的部分:包含异常类型和详细信息的行。这通常是回溯的最后一行,但如果异常的详细信息跨越多行,它也可以延伸到多行:

>>> raise ValueError('multi\n    line\ndetail')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: multi
    line
detail

最后三行(以 ValueError 开头)会与异常的类型和详细信息进行比较,其余的则被忽略。

最佳实践是省略回溯堆栈,除非它为示例增加了重要的文档价值。所以最后一个示例可能这样写更好:

>>> raise ValueError('multi\n    line\ndetail')
Traceback (most recent call last):
    ...
ValueError: multi
    line
detail

请注意,回溯信息被特殊处理。特别是在重写的示例中,... 的使用与 doctest 的 ELLIPSIS 选项无关。该示例中的省略号可以省略,也可以是三个(或三百个)逗号或数字,或是一段缩进的 Monty Python 小品剧本。

一些你应该读一遍但不需要记住的细节:

  • Doctest 无法猜测你的预期输出是来自异常回溯还是普通打印。所以,例如,一个期望输出为 ValueError: 42 is prime 的示例,无论 ValueError 是否真的被引发,还是示例仅仅打印了那段回溯文本,都会通过。在实践中,普通输出很少以回溯头行开始,所以这不会造成实际问题。

  • 回溯堆栈的每一行(如果存在)必须比示例的第一行缩进更多,*或者*以非字母数字字符开头。回溯头之后的第一行,如果缩进相同且以字母数字开头,则被视为异常详细信息的开始。当然,这对于真正的回溯是正确的。

  • 当指定了 IGNORE_EXCEPTION_DETAIL doctest 选项时,异常名称中最左边的冒号之后的所有内容以及任何模块信息都会被忽略。

  • 对于某些 SyntaxError,交互式 shell 会省略回溯头行。但 doctest 使用回溯头行来区分异常和非异常。因此,在极少数情况下,当你需要测试一个省略了回溯头行的 SyntaxError 时,你需要手动将回溯头行添加到你的测试示例中。

  • 对于某些异常,Python 使用 ^ 标记和波浪线来显示错误的位置:

    >>> 1 + None
      File "<stdin>", line 1
        1 + None
        ~~^~~~~~
    TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
    

    由于显示错误位置的行出现在异常类型和详细信息之前,它们不会被 doctest 检查。例如,下面的测试会通过,即使它把 ^ 标记放在了错误的位置:

    >>> 1 + None
      File "<stdin>", line 1
        1 + None
        ^~~~~~~~
    TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
    

选项标志

许多选项标志控制着 doctest 行为的各个方面。这些标志的符号名称作为模块常量提供,可以通过按位或运算组合在一起,并传递给各种函数。这些名称也可以在doctest 指令中使用,并且可以通过 -o 选项传递给 doctest 命令行界面。

第一组选项定义了测试语义,控制 doctest 如何决定实际输出是否与示例的预期输出匹配的各个方面:

doctest.DONT_ACCEPT_TRUE_FOR_1

默认情况下,如果一个预期输出块只包含 1,那么一个只包含 1 或只包含 True 的实际输出块被认为是匹配的,同样适用于 0False。当指定了 DONT_ACCEPT_TRUE_FOR_1 时,这两种替换都不被允许。默认行为是为了适应 Python 将许多函数的返回类型从整数改为布尔值;期望“小整数”输出的 doctest 在这些情况下仍然有效。这个选项可能会在未来移除,但不是在几年内。

doctest.DONT_ACCEPT_BLANKLINE

默认情况下,如果预期输出块中有一行只包含字符串 <BLANKLINE>,那么该行将与实际输出中的一个空行匹配。因为一个真正的空行会界定预期输出,这是传达期望一个空行的唯一方式。当指定了 DONT_ACCEPT_BLANKLINE 时,这种替换是不允许的。

doctest.NORMALIZE_WHITESPACE

当指定时,所有空白序列(空格和换行符)都被视为相等。预期输出中的任何空白序列将与实际输出中的任何空白序列匹配。默认情况下,空白必须精确匹配。NORMALIZE_WHITESPACE 在预期输出的一行非常长,并且你希望将其分成多行显示在源代码中时特别有用。

doctest.ELLIPSIS

当指定时,预期输出中的省略号标记(...)可以匹配实际输出中的任何子字符串。这包括跨越行边界的子字符串和空子字符串,所以最好简单地使用它。复杂的使用方式可能会导致与正则表达式中 .* 容易出现的“哎呀,匹配得太多了!”的意外情况类似。

doctest.IGNORE_EXCEPTION_DETAIL

当指定时,期望异常的 doctest 只要引发了预期类型的异常就会通过,即使详细信息(消息和完全限定的异常名称)不匹配。

例如,一个期望 ValueError: 42 的示例,如果实际引发的异常是 ValueError: 3*14 就会通过,但如果引发了 TypeError 就会失败。它还会忽略异常类之前的任何完全限定名称,这些名称在不同的实现、Python 版本以及使用的代码/库之间可能会有所不同。因此,在使用此标志的情况下,以下三种变体都将起作用:

>>> raise Exception('message')
Traceback (most recent call last):
Exception: message

>>> raise Exception('message')
Traceback (most recent call last):
builtins.Exception: message

>>> raise Exception('message')
Traceback (most recent call last):
__main__.Exception: message

请注意,ELLIPSIS 也可以用来忽略异常消息的细节,但这样的测试仍可能因为模块名称是否存在或是否完全匹配而失败。

在 3.2 版更改: IGNORE_EXCEPTION_DETAIL 现在也会忽略与被测试异常所在模块相关的任何信息。

doctest.SKIP

当指定时,完全不运行该示例。这在 doctest 示例既作为文档又作为测试用例,并且某个示例应出于文档目的被包含,但不应被检查的情况下非常有用。例如,示例的输出可能是随机的;或者示例可能依赖于测试驱动程序不可用的资源。

SKIP 标志也可以用于临时“注释掉”示例。

doctest.COMPARISON_FLAGS

一个位掩码,将上述所有比较标志进行“或”运算组合在一起。

第二组选项控制如何报告测试失败:

doctest.REPORT_UDIFF

当指定时,涉及多行预期和实际输出的失败将使用统一差异(unified diff)格式显示。

doctest.REPORT_CDIFF

当指定时,涉及多行预期和实际输出的失败将使用上下文差异(context diff)格式显示。

doctest.REPORT_NDIFF

当指定时,差异由 difflib.Differ 计算,使用与流行的 ndiff.py 工具相同的算法。这是唯一一种既能标记行内差异又能标记跨行差异的方法。例如,如果一行预期输出包含数字 1,而实际输出包含字母 l,则会插入一行,用一个插入符号标记不匹配的列位置。

doctest.REPORT_ONLY_FIRST_FAILURE

当指定时,显示每个 doctest 中的第一个失败示例,但抑制所有其余示例的输出。这将防止 doctest 报告因早期失败而中断的正确示例;但它也可能隐藏独立于第一个失败而失败的不正确示例。当指定 REPORT_ONLY_FIRST_FAILURE 时,其余示例仍然会运行,并计入报告的总失败数中;只有输出被抑制。

doctest.FAIL_FAST

当指定时,在第一个失败的示例后退出,不尝试运行其余的示例。因此,报告的失败数最多为1。这个标志在调试时可能很有用,因为第一个失败后的示例甚至不会产生调试输出。

doctest.REPORTING_FLAGS

一个位掩码,将上述所有报告标志进行“或”运算组合在一起。

还有一种方法可以注册新的选项标志名称,但这除非你打算通过子类化来扩展 doctest 的内部机制,否则用处不大:

doctest.register_optionflag(name)

使用给定的名称创建一个新的选项标志,并返回新标志的整数值。register_optionflag() 可以在子类化 OutputCheckerDocTestRunner 时使用,以创建你的子类支持的新选项。register_optionflag() 应始终使用以下惯用法调用:

MY_FLAG = register_optionflag('MY_FLAG')

指令

Doctest 指令可用于修改单个示例的选项标志。Doctest 指令是跟在示例源代码后面的特殊 Python 注释:

directive:             "#" "doctest:" directive_options
directive_options:     directive_option ("," directive_option)*
directive_option:      on_or_off directive_option_name
on_or_off:             "+" | "-"
directive_option_name: "DONT_ACCEPT_BLANKLINE" | "NORMALIZE_WHITESPACE" | ...

+- 与指令选项名称之间不允许有空格。指令选项名称可以是上面解释的任何选项标志名称。

一个示例的 doctest 指令会修改 doctest 对该单个示例的行为。使用 + 来启用指定的行为,或使用 - 来禁用它。

例如,这个测试会通过:

>>> print(list(range(20)))  # doctest: +NORMALIZE_WHITESPACE
[0,   1,  2,  3,  4,  5,  6,  7,  8,  9,
10,  11, 12, 13, 14, 15, 16, 17, 18, 19]

如果没有这个指令,它会失败,因为实际输出在单位数列表元素前没有两个空格,并且实际输出在单行上。这个测试也会通过,并且也需要一个指令才能这样做:

>>> print(list(range(20)))  # doctest: +ELLIPSIS
[0, 1, ..., 18, 19]

可以在单个物理行上使用多个指令,用逗号分隔:

>>> print(list(range(20)))  # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
[0,    1, ...,   18,    19]

如果单个示例使用了多个指令注释,那么它们会被合并:

>>> print(list(range(20)))  # doctest: +ELLIPSIS
...                         # doctest: +NORMALIZE_WHITESPACE
[0,    1, ...,   18,    19]

如前一个示例所示,你可以向示例中添加只包含指令的 ... 行。当一个示例太长,指令无法舒适地放在同一行时,这可能很有用:

>>> print(list(range(5)) + list(range(10, 20)) + list(range(30, 40)))
... # doctest: +ELLIPSIS
[0, ..., 4, 10, ..., 19, 30, ..., 39]

请注意,由于所有选项默认都是禁用的,并且指令仅适用于它们所在的示例,因此启用选项(通过指令中的 +)通常是唯一有意义的选择。但是,选项标志也可以传递给运行 doctest 的函数,从而建立不同的默认值。在这种情况下,通过指令中的 - 禁用一个选项可能很有用。

警告

doctest 对预期输出的精确匹配要求非常严格。即使只有一个字符不匹配,测试也会失败。在你学习 Python 对输出的保证和不保证的确切内容时,这可能会让你惊讶几次。例如,打印一个集合时,Python 不保证元素以任何特定顺序打印,所以像这样的测试:

>>> foo()
{"spam", "eggs"}

是脆弱的!一种解决方法是这样做:

>>> foo() == {"spam", "eggs"}
True

另一种方法是:

>>> d = sorted(foo())
>>> d
['eggs', 'spam']

还有其他方法,但你应该明白这个意思了。

另一个不好的想法是打印嵌入了对象地址的东西,比如:

>>> id(1.0)  # certain to fail some of the time
7948648
>>> class C: pass
>>> C()  # the default repr() for instances embeds an address
<C object at 0x00AC18F0>

ELLIPSIS 指令为最后一个例子提供了一个很好的方法:

>>> C()  # doctest: +ELLIPSIS
<C object at 0x...>

浮点数在不同平台上的输出也可能存在微小差异,因为 Python 依赖于平台的 C 库进行一些浮点数计算,而 C 库在这方面的质量差异很大。

>>> 1000**0.1  # risky
1.9952623149688797
>>> round(1000**0.1, 9) # safer
1.995262315
>>> print(f'{1000**0.1:.4f}') # much safer
1.9953

形如 I/2.**J 的数字在所有平台上都是安全的,我经常设计 doctest 示例来产生这种形式的数字:

>>> 3./4  # utterly safe
0.75

简单的分数也更容易让人理解,这使得文档更好。

基本 API

函数 testmod()testfile() 提供了 doctest 的一个简单接口,对于大多数基本用途来说应该足够了。关于这两个函数的非正式介绍,请参见 简单用法:检查文档字符串中的示例简单用法:检查文本文件中的示例 部分。

doctest.testfile(filename, module_relative=True, name=None, package=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, parser=DocTestParser(), encoding=None)

除了 *filename* 外,所有参数都是可选的,并且应该以关键字形式指定。

测试名为 *filename* 的文件中的示例。返回 (failure_count, test_count)

可选参数 *module_relative* 指定了如何解释文件名:

  • 如果 *module_relative* 是 True(默认值),那么 *filename* 指定一个与操作系统无关的、相对于模块的路径。默认情况下,此路径相对于调用模块的目录;但如果指定了 *package* 参数,则它相对于该包。为了确保与操作系统无关,*filename* 应使用 / 字符来分隔路径段,并且不能是绝对路径(即,不能以 / 开头)。

  • 如果 *module_relative* 为 False,则 *filename* 指定一个特定于操作系统的路径。路径可以是绝对的或相对的;相对路径是相对于当前工作目录解析的。

可选参数 *name* 给出测试的名称;默认情况下,或如果为 None,则使用 os.path.basename(filename)

可选参数 *package* 是一个 Python 包或 Python 包的名称,其目录应用作模块相对文件名的基目录。如果没有指定包,则调用模块的目录将用作模块相对文件名的基目录。如果 *module_relative* 为 False,则指定 *package* 是一个错误。

可选参数 *globs* 提供一个字典,用作执行示例时的全局变量。会为 doctest 创建此字典的一个新的浅拷贝,因此其示例以一个干净的状态开始。默认情况下,或如果为 None,则使用一个新的空字典。

可选参数 *extraglobs* 提供了一个合并到用于执行示例的全局变量中的字典。这类似于 dict.update():如果 *globs* 和 *extraglobs* 有一个共同的键,那么 *extraglobs* 中关联的值会出现在合并后的字典中。默认情况下,或如果为 None,则不使用额外的全局变量。这是一个高级功能,允许对 doctest 进行参数化。例如,可以为一个基类编写 doctest,使用一个通用的类名,然后通过传递一个将通用名称映射到要测试的子类的 *extraglobs* 字典来重用它来测试任意数量的子类。

可选参数 *verbose* 如果为真,则打印大量信息,如果为假,则仅打印失败信息;默认情况下,或如果为 None,当且仅当 '-v'sys.argv 中时为真。

可选参数 *report* 在为真时在末尾打印摘要,否则在末尾不打印任何内容。在详细模式下,摘要是详细的,否则摘要非常简短(事实上,如果所有测试都通过,则为空)。

可选参数 *optionflags*(默认值 0)接受选项标志的按位或。请参见 选项标志 部分。

可选参数 *raise_on_error* 默认为 false。如果为 true,则在示例中出现第一个失败或意外异常时会引发异常。这允许对失败进行事后调试。默认行为是继续运行示例。

可选参数 *parser* 指定一个 DocTestParser(或其子类),用于从文件中提取测试。它默认为一个普通的解析器(即 DocTestParser())。

可选参数 *encoding* 指定了一个应在将文件转换为 unicode 时使用的编码。

doctest.testmod(m=None, name=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False)

所有参数都是可选的,除了 *m* 之外,都应以关键字形式指定。

测试从模块 *m*(或模块 __main__,如果 *m* 未提供或为 None)可达的函数和类中的文档字符串中的示例,从 m.__doc__ 开始。

如果存在,也测试从字典 m.__test__ 可达的示例。m.__test__ 将名称(字符串)映射到函数、类和字符串;函数和类的文档字符串会被搜索示例;字符串则直接被搜索,就像它们是文档字符串一样。

只搜索附加到属于模块 *m* 的对象的文档字符串。

返回 (failure_count, test_count)

可选参数 *name* 给出模块的名称;默认情况下,或如果为 None,则使用 m.__name__

可选参数 *exclude_empty* 默认为 false。如果为 true,则未找到 doctest 的对象将从考虑中排除。默认值是一个向后兼容的技巧,以便仍在结合 testmod() 使用 doctest.master.summarize 的代码继续为没有测试的对象获取输出。较新的 DocTestFinder 构造函数的 *exclude_empty* 参数默认为 true。

可选参数 *extraglobs*、*verbose*、*report*、*optionflags*、*raise_on_error* 和 *globs* 与上面的函数 testfile() 相同,只是 *globs* 默认为 m.__dict__

doctest.run_docstring_examples(f, globs, verbose=False, name='NoName', compileflags=None, optionflags=0)

测试与对象 *f* 关联的示例;例如,*f* 可以是字符串、模块、函数或类对象。

字典参数 *globs* 的浅拷贝用作执行上下文。

可选参数 *name* 用于失败消息,默认为 "NoName"

如果可选参数 *verbose* 为真,即使没有失败也会生成输出。默认情况下,仅在示例失败时生成输出。

可选参数 *compileflags* 给出在运行示例时 Python 编译器应使用的一组标志。默认情况下,或如果为 None,则根据在 *globs* 中找到的未来特性集推断标志。

可选参数 *optionflags* 的作用与上面的函数 testfile() 相同。

Unittest API

随着你收集的 doctest 模块越来越多,你会想要一种系统地运行所有 doctest 的方法。doctest 提供了两个函数,可用于从包含 doctest 的模块和文本文件中创建 unittest 测试套件。要与 unittest 测试发现集成,请在你的测试模块中包含一个 load_tests 函数:

import unittest
import doctest
import my_module_with_doctests

def load_tests(loader, tests, ignore):
    tests.addTests(doctest.DocTestSuite(my_module_with_doctests))
    return tests

有两个主要函数用于从包含 doctest 的文本文件和模块创建 unittest.TestSuite 实例:

doctest.DocFileSuite(*paths, module_relative=True, package=None, setUp=None, tearDown=None, globs=None, optionflags=0, parser=DocTestParser(), encoding=None)

将一个或多个文本文件中的 doctest 测试转换为 unittest.TestSuite

返回的 unittest.TestSuite 将由 unittest 框架运行,并运行每个文件中的交互式示例。如果任何文件中的示例失败,则合成的单元测试失败,并引发一个 failureException 异常,显示包含测试的文件名和(有时是近似的)行号。如果一个文件中的所有示例都被跳过,则合成的单元测试也被标记为跳过。

传递一个或多个要检查的文本文件的路径(作为字符串)。

选项可以作为关键字参数提供:

可选参数 *module_relative* 指定如何解释 *paths* 中的文件名:

  • 如果 *module_relative* 是 True(默认值),那么 *paths* 中的每个文件名都指定一个与操作系统无关的、相对于模块的路径。默认情况下,此路径相对于调用模块的目录;但如果指定了 *package* 参数,则它相对于该包。为了确保与操作系统无关,每个文件名应使用 / 字符来分隔路径段,并且不能是绝对路径(即,不能以 / 开头)。

  • 如果 *module_relative* 为 False,则 *paths* 中的每个文件名都指定一个特定于操作系统的路径。路径可以是绝对的或相对的;相对路径是相对于当前工作目录解析的。

可选参数 *package* 是一个 Python 包或 Python 包的名称,其目录应用作 *paths* 中模块相对文件名的基目录。如果没有指定包,则调用模块的目录将用作模块相对文件名的基目录。如果 *module_relative* 为 False,则指定 *package* 是一个错误。

可选参数 *setUp* 为测试套件指定一个设置函数。它在运行每个文件中的测试之前被调用。*setUp* 函数将被传递一个 DocTest 对象。*setUp* 函数可以访问测试的全局变量,即传递的测试对象的 globs 属性。

可选参数 *tearDown* 为测试套件指定一个拆卸函数。它在运行每个文件中的测试之后被调用。*tearDown* 函数将被传递一个 DocTest 对象。*tearDown* 函数可以访问测试的全局变量,即传递的测试对象的 globs 属性。

可选参数 *globs* 是一个包含测试初始全局变量的字典。会为每个测试创建这个字典的一个新副本。默认情况下,*globs* 是一个新的空字典。

可选参数 *optionflags* 指定测试的默认 doctest 选项,通过将单个选项标志进行或运算创建。请参见 选项标志 部分。关于设置报告选项的更好方法,请参见下面的 set_unittest_reportflags() 函数。

可选参数 *parser* 指定一个 DocTestParser(或其子类),用于从文件中提取测试。它默认为一个普通的解析器(即 DocTestParser())。

可选参数 *encoding* 指定了一个应在将文件转换为 unicode 时使用的编码。

全局变量 __file__ 会被添加到使用 DocFileSuite() 从文本文件加载的 doctest 的全局变量中。

doctest.DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, setUp=None, tearDown=None, optionflags=0, checker=None)

将一个模块的 doctest 测试转换为一个 unittest.TestSuite

返回的 unittest.TestSuite 将由 unittest 框架运行,并运行模块中的每个 doctest。每个文档字符串作为单独的单元测试运行。如果任何 doctest 失败,则合成的单元测试失败,并引发一个 unittest.TestCase.failureException 异常,显示包含测试的文件名和(有时是近似的)行号。如果一个文档字符串中的所有示例都被跳过,则

可选参数 *module* 提供要测试的模块。它可以是一个模块对象或一个(可能是带点的)模块名称。如果未指定,则使用调用此函数的模块。

可选参数 *globs* 是一个包含测试初始全局变量的字典。会为每个测试创建这个字典的一个新副本。默认情况下,*globs* 是模块的 __dict__

可选参数 *extraglobs* 指定一组额外的全局变量,它会合并到 *globs* 中。默认情况下,不使用额外的全局变量。

可选参数 *test_finder* 是用于从模块中提取 doctest 的 DocTestFinder 对象(或其替代品)。

可选参数 setUptearDownoptionflags 与上面的函数 DocFileSuite() 相同,但它们是为每个文档字符串调用的。

此函数使用与 testmod() 相同的搜索技术。

在 3.5 版更改: 如果 module 不包含文档字符串,DocTestSuite() 会返回一个空的 unittest.TestSuite,而不是引发 ValueError

在底层,DocTestSuite()doctest.DocTestCase 实例创建了一个 unittest.TestSuite,而 DocTestCaseunittest.TestCase 的子类。这里没有记录 DocTestCase(它是一个内部细节),但研究其代码可以回答有关 unittest 集成确切细节的问题。

同样,DocFileSuite()doctest.DocFileCase 实例创建一个 unittest.TestSuite,而 DocFileCaseDocTestCase 的子类。

因此,创建 unittest.TestSuite 的两种方式都运行 DocTestCase 的实例。这有一个微妙的重要原因:当您自己运行 doctest 函数时,可以通过向 doctest 函数传递选项标志来直接控制正在使用的 doctest 选项。但是,如果您正在编写一个 unittest 框架,unittest 最终控制着测试的运行时间和方式。框架作者通常希望控制 doctest 的报告选项(例如,可能由命令行选项指定),但没有办法通过 unittest 将选项传递给 doctest 的测试运行器。

因此,doctest 还通过此函数支持特定于 unittest 支持的 doctest 报告标志的概念

doctest.set_unittest_reportflags(flags)

设置要使用的 doctest 报告标志。

参数 flags 接受选项标志的按位或。请参阅 选项标志 部分。只能使用“报告标志”。

这是一个模块全局设置,会影响 unittest 模块运行的所有未来 doctest:DocTestCaserunTest() 方法会查看构造 DocTestCase 实例时为测试用例指定的选项标志。如果未指定报告标志(这是典型和预期的情况),doctestunittest 报告标志会按位或到选项标志中,然后将如此增强的选项标志传递给为运行 doctest 而创建的 DocTestRunner 实例。如果在构造 DocTestCase 实例时指定了任何报告标志,则会忽略 doctestunittest 报告标志。

该函数会返回在调用函数之前有效的 unittest 报告标志的值。

高级 API

基本 API 是一个简单的包装器,旨在使 doctest 易于使用。它相当灵活,应该能满足大多数用户的需求;但是,如果您需要对测试进行更精细的控制,或希望扩展 doctest 的功能,则应使用高级 API。

高级 API 围绕两个容器类构建,它们用于存储从 doctest 用例中提取的交互式示例

  • Example:单个 Python 语句,与其预期输出配对。

  • DocTestExample 的集合,通常从单个文档字符串或文本文件中提取。

定义了额外的处理类来查找、解析、运行和检查 doctest 示例

这些处理类之间的关系总结在下图中

                            list of:
+------+                   +---------+
|module| --DocTestFinder-> | DocTest | --DocTestRunner-> results
+------+    |        ^     +---------+     |       ^    (printed)
            |        |     | Example |     |       |
            v        |     |   ...   |     v       |
           DocTestParser   | Example |   OutputChecker
                           +---------+

DocTest 对象

class doctest.DocTest(examples, globs, name, filename, lineno, docstring)

应在单个命名空间中运行的 doctest 示例的集合。构造函数参数用于初始化同名属性。

DocTest 定义了以下属性。它们由构造函数初始化,不应直接修改。

examples

一个 Example 对象列表,编码了此测试应运行的各个交互式 Python 示例。

globs

示例应在其中运行的命名空间(也称为全局变量)。这是一个将名称映射到值的字典。示例对命名空间所做的任何更改(例如绑定新变量)都将在测试运行后反映在 globs 中。

name

标识 DocTest 的字符串名称。通常,这是从中提取测试的对象或文件的名称。

filename

从中提取此 DocTest 的文件的名称;如果文件名未知,或者 DocTest 不是从文件中提取的,则为 None

lineno

DocTestfilename 内开始的行号;如果行号不可用,则为 None。此行号是相对于文件开头的零基行号。

docstring

从中提取测试的字符串;如果字符串不可用,或者测试不是从字符串中提取的,则为 None

Example 对象

class doctest.Example(source, want, exc_msg=None, lineno=0, indent=0, options=None)

单个交互式示例,由一个 Python 语句及其预期输出组成。构造函数参数用于初始化同名属性。

Example 定义了以下属性。它们由构造函数初始化,不应直接修改。

source

包含示例源代码的字符串。此源代码由单个 Python 语句组成,并始终以换行符结尾;构造函数在必要时会添加换行符。

want

运行示例源代码的预期输出(来自 stdout,或在异常情况下的回溯信息)。除非没有预期输出,否则 want 以换行符结尾,在这种情况下,它是一个空字符串。构造函数在必要时会添加换行符。

exc_msg

如果示例预期生成异常,则为该示例生成的异常消息;如果不预期生成异常,则为 None。此异常消息与 traceback.format_exception_only() 的返回值进行比较。exc_msg 以换行符结尾,除非它是 None。构造函数在需要时会添加换行符。

lineno

在包含此示例的字符串中,该示例开始的行号。此行号是相对于包含字符串开头的零基行号。

indent

示例在包含字符串中的缩进,即示例第一个提示符之前的空格字符数。

options

一个从选项标志映射到 TrueFalse 的字典,用于为此示例覆盖默认选项。此字典中未包含的任何选项标志都保留其默认值(由 DocTestRunneroptionflags 指定)。默认情况下,不设置任何选项。

DocTestFinder 对象

class doctest.DocTestFinder(verbose=False, parser=DocTestParser(), recurse=True, exclude_empty=True)

一个处理类,用于从给定对象的文档字符串及其包含对象的文档字符串中提取相关的 DocTests。DocTests 可以从模块、类、函数、方法、静态方法、类方法和属性中提取。

可选参数 verbose 可用于显示查找器搜索的对象。它默认为 False(无输出)。

可选参数 parser 指定用于从文档字符串中提取 doctest 的 DocTestParser 对象(或其替代品)。

如果可选参数 recurse 为 false,则 DocTestFinder.find() 将只检查给定的对象,而不检查任何包含的对象。

如果可选参数 exclude_empty 为 false,则 DocTestFinder.find() 将包含文档字符串为空的对象的测试。

DocTestFinder 定义了以下方法

find(obj[, name][, module][, globs][, extraglobs])

返回由 obj 的文档字符串或其任何包含对象的文档字符串定义的 DocTests 列表。

可选参数 name 指定对象的名称;此名称将用于构造返回的 DocTests 的名称。如果未指定 name,则使用 obj.__name__

可选参数 module 是包含给定对象的模块。如果未指定模块或是 None,则测试查找器将尝试自动确定正确的模块。对象的模块用于

  • 作为默认命名空间,如果未指定 globs

  • 防止 DocTestFinder 从其他模块导入的对象中提取 DocTests。(模块不同于 module 的包含对象将被忽略。)

  • 查找包含对象的文件名。

  • 帮助在其文件中查找对象的行号。

如果 moduleFalse,则不会尝试查找模块。这很晦涩,主要用于测试 doctest 本身:如果 moduleFalse,或是 None 但无法自动找到,则所有对象都被认为属于(不存在的)模块,因此将(递归地)搜索所有包含的对象以查找 doctest。

每个 DocTest 的全局变量是通过组合 globsextraglobs 形成的(extraglobs 中的绑定会覆盖 globs 中的绑定)。为每个 DocTest 创建一个新的全局字典浅拷贝。如果未指定 globs,则默认为模块的 __dict__(如果已指定),否则为 {}。如果未指定 extraglobs,则默认为 {}

DocTestParser 对象

class doctest.DocTestParser

一个处理类,用于从字符串中提取交互式示例,并使用它们创建一个 DocTest 对象。

DocTestParser 定义了以下方法

get_doctest(string, globs, name, filename, lineno)

从给定字符串中提取所有 doctest 示例,并将它们收集到一个 DocTest 对象中。

globsnamefilenamelineno 是新的 DocTest 对象的属性。有关更多信息,请参阅 DocTest 的文档。

get_examples(string, name='<string>')

从给定字符串中提取所有 doctest 示例,并将它们作为 Example 对象列表返回。行号是 0-based 的。可选参数 name 是标识此字符串的名称,仅用于错误消息。

parse(string, name='<string>')

将给定字符串划分为示例和中间文本,并将它们作为交替的 Examples 和字符串列表返回。Examples 的行号是 0-based 的。可选参数 name 是标识此字符串的名称,仅用于错误消息。

TestResults 对象

class doctest.TestResults(failed, attempted)
failed

失败的测试数。

attempted

尝试的测试数。

skipped

跳过的测试数。

在 3.13 版本加入。

DocTestRunner 对象

class doctest.DocTestRunner(checker=None, verbose=None, optionflags=0)

一个处理类,用于执行和验证 DocTest 中的交互式示例。

预期输出和实际输出之间的比较由 OutputChecker 完成。可以使用多个选项标志来自定义此比较;有关更多信息,请参阅 选项标志 部分。如果选项标志不足,也可以通过向构造函数传递 OutputChecker 的子类来自定义比较。

测试运行器的显示输出可以通过两种方式控制。首先,可以将输出函数传递给 run();此函数将使用应显示的字符串调用。它默认为 sys.stdout.write。如果捕获输出不足,也可以通过子类化 DocTestRunner 并覆盖 report_start()report_success()report_unexpected_exception()report_failure() 方法来自定义显示输出。

可选关键字参数 checker 指定应用于比较 doctest 示例的预期输出与实际输出的 OutputChecker 对象(或其替代品)。

可选关键字参数 verbose 控制 DocTestRunner 的详细程度。如果 verboseTrue,则在运行时会打印有关每个示例的信息。如果 verboseFalse,则只打印失败信息。如果未指定 verbose 或为 None,则仅当使用命令行开关 -v 时才使用详细输出。

可选关键字参数 optionflags 可用于控制测试运行器如何比较预期输出与实际输出,以及如何显示失败。有关更多信息,请参阅 选项标志 部分。

测试运行器会累积统计信息。尝试、失败和跳过的示例的总数也可以通过 triesfailuresskips 属性获得。run()summarize() 方法返回一个 TestResults 实例。

DocTestRunner 定义了以下方法

report_start(out, test, example)

报告测试运行器即将处理给定的示例。提供此方法是为了允许 DocTestRunner 的子类自定义其输出;不应直接调用它。

example 是即将处理的示例。test 是包含 example 的测试。out 是传递给 DocTestRunner.run() 的输出函数。

report_success(out, test, example, got)

报告给定的示例成功运行。提供此方法是为了允许 DocTestRunner 的子类自定义其输出;不应直接调用它。

example 是即将处理的示例。got 是示例的实际输出。test 是包含 example 的测试。out 是传递给 DocTestRunner.run() 的输出函数。

report_failure(out, test, example, got)

报告给定的示例失败。提供此方法是为了允许 DocTestRunner 的子类自定义其输出;不应直接调用它。

example 是即将处理的示例。got 是示例的实际输出。test 是包含 example 的测试。out 是传递给 DocTestRunner.run() 的输出函数。

report_unexpected_exception(out, test, example, exc_info)

报告给定的示例引发了意外的异常。提供此方法是为了允许 DocTestRunner 的子类自定义其输出;不应直接调用它。

example 是即将处理的示例。exc_info 是一个包含有关意外异常信息的元组(由 sys.exc_info() 返回)。test 是包含 example 的测试。out 是传递给 DocTestRunner.run() 的输出函数。

run(test, compileflags=None, out=None, clear_globs=True)

运行 test(一个 DocTest 对象)中的示例,并使用写入器函数 out 显示结果。返回一个 TestResults 实例。

示例在命名空间 test.globs 中运行。如果 clear_globs 为 true(默认值),则此命名空间将在测试运行后被清除,以帮助进行垃圾回收。如果您想在测试完成后检查命名空间,则使用 clear_globs=False

compileflags 给出运行示例时 Python 编译器应使用的标志集。如果未指定,则默认为适用于 globs 的 future-import 标志集。

每个示例的输出都使用 DocTestRunner 的输出检查器进行检查,结果由 DocTestRunner.report_*() 方法格式化。

summarize(verbose=None)

打印此 DocTestRunner 已运行的所有测试用例的摘要,并返回一个 TestResults 实例。

可选的 verbose 参数控制摘要的详细程度。如果未指定详细程度,则使用 DocTestRunner 的详细程度。

DocTestParser 具有以下属性

tries

尝试的示例数。

failures

失败的示例数。

skips

跳过的示例数。

在 3.13 版本加入。

OutputChecker 对象

class doctest.OutputChecker

用于检查 doctest 示例的实际输出是否与预期输出匹配的类。OutputChecker 定义了两种方法:check_output(),它比较给定的一对输出,如果它们匹配则返回 True;以及 output_difference(),它返回一个描述两个输出之间差异的字符串。

OutputChecker 定义了以下方法

check_output(want, got, optionflags)

当且仅当示例的实际输出(got)与预期输出(want)匹配时返回 True。如果这些字符串完全相同,则始终认为它们匹配;但根据测试运行器使用的选项标志,也可能有几种非精确匹配类型。有关选项标志的更多信息,请参阅 选项标志 部分。

output_difference(example, got, optionflags)

返回一个字符串,描述给定示例的预期输出(example)与实际输出(got)之间的差异。optionflags 是用于比较 wantgot 的选项标志集。

调试

Doctest 提供了几种调试 doctest 示例的机制

  • 有几个函数可以将 doctest 转换为可执行的 Python 程序,这些程序可以在 Python 调试器 pdb 下运行。

  • DebugRunner 类是 DocTestRunner 的一个子类,它会为第一个失败的示例引发一个异常,其中包含有关该示例的信息。此信息可用于对该示例进行事后调试。

  • DocTestSuite() 生成的 unittest 用例支持 unittest.TestCase 定义的 debug() 方法。

  • 您可以在 doctest 示例中添加对 pdb.set_trace() 的调用,当该行被执行时,您将进入 Python 调试器。然后您可以检查变量的当前值等等。例如,假设 a.py 只包含这个模块文档字符串

    """
    >>> def f(x):
    ...     g(x*2)
    >>> def g(x):
    ...     print(x+3)
    ...     import pdb; pdb.set_trace()
    >>> f(3)
    9
    """
    

    那么一个交互式 Python 会话可能看起来像这样

    >>> import a, doctest
    >>> doctest.testmod(a)
    --Return--
    > <doctest a[1]>(3)g()->None
    -> import pdb; pdb.set_trace()
    (Pdb) list
      1     def g(x):
      2         print(x+3)
      3  ->     import pdb; pdb.set_trace()
    [EOF]
    (Pdb) p x
    6
    (Pdb) step
    --Return--
    > <doctest a[0]>(2)f()->None
    -> g(x*2)
    (Pdb) list
      1     def f(x):
      2  ->     g(x*2)
    [EOF]
    (Pdb) p x
    3
    (Pdb) step
    --Return--
    > <doctest a[2]>(1)?()->None
    -> f(3)
    (Pdb) cont
    (0, 3)
    >>>
    

将 doctest 转换为 Python 代码,并可能在调试器下运行合成代码的函数

doctest.script_from_examples(s)

将带有示例的文本转换为脚本。

参数 s 是一个包含 doctest 示例的字符串。该字符串被转换为一个 Python 脚本,其中 s 中的 doctest 示例被转换为常规代码,其他所有内容都被转换为 Python 注释。生成的脚本作为字符串返回。例如,

import doctest
print(doctest.script_from_examples(r"""
    Set x and y to 1 and 2.
    >>> x, y = 1, 2

    Print their sum:
    >>> print(x+y)
    3
"""))

显示

# Set x and y to 1 and 2.
x, y = 1, 2
#
# Print their sum:
print(x+y)
# Expected:
## 3

此函数由其他函数(见下文)在内部使用,但当您想将交互式 Python 会话转换为 Python 脚本时也很有用。

doctest.testsource(module, name)

将对象的 doctest 转换为脚本。

参数 module 是一个模块对象,或包含感兴趣的 doctest 对象的模块的点分名称。参数 name 是(模块内)具有感兴趣的 doctest 的对象的名称。结果是一个字符串,包含对象的文档字符串转换为 Python 脚本,如上文 script_from_examples() 所述。例如,如果模块 a.py 包含一个顶层函数 f(),那么

import a, doctest
print(doctest.testsource(a, "a.f"))

打印函数 f() 的文档字符串的脚本版本,其中 doctest 转换为代码,其余部分放在注释中。

doctest.debug(module, name, pm=False)

调试对象的 doctest。

modulename 参数与上面的函数 testsource() 相同。为命名对象的文档字符串合成的 Python 脚本被写入一个临时文件,然后该文件在 Python 调试器 pdb 的控制下运行。

module.__dict__ 的浅拷贝用于本地和全局执行上下文。

可选参数 pm 控制是否使用事后调试。如果 pm 具有真值,则直接运行脚本文件,并且只有在脚本通过引发未处理的异常终止时,调试器才会介入。如果发生这种情况,则通过 pdb.post_mortem() 调用事后调试,并传递来自未处理异常的回溯对象。如果未指定 pm 或为 false,则从一开始就在调试器下运行脚本,通过将适当的 exec() 调用传递给 pdb.run()

doctest.debug_src(src, pm=False, globs=None)

调试字符串中的 doctest。

这类似于上面的函数 debug(),只是直接通过 src 参数指定包含 doctest 示例的字符串。

可选参数 pm 与上面的函数 debug() 中的含义相同。

可选参数 globs 给出一个字典,用作本地和全局执行上下文。如果未指定或为 None,则使用空字典。如果指定,则使用该字典的浅拷贝。

DebugRunner 类及其可能引发的特殊异常,对测试框架作者最感兴趣,这里只做简要介绍。有关更多详细信息,请参阅源代码,尤其是 DebugRunner 的文档字符串(它是一个 doctest!)

class doctest.DebugRunner(checker=None, verbose=None, optionflags=0)

DocTestRunner 的一个子类,它在遇到失败时立即引发异常。如果发生意外异常,则会引发 UnexpectedException 异常,其中包含测试、示例和原始异常。如果输出不匹配,则会引发 DocTestFailure 异常,其中包含测试、示例和实际输出。

有关构造函数参数和方法的信息,请参阅 高级 API 部分中 DocTestRunner 的文档。

DebugRunner 实例可能引发两种异常

exception doctest.DocTestFailure(test, example, got)

DocTestRunner 引发的异常,表示 doctest 示例的实际输出与其预期输出不匹配。构造函数参数用于初始化同名属性。

DocTestFailure 定义了以下属性

DocTestFailure.test

示例失败时正在运行的 DocTest 对象。

DocTestFailure.example

失败的 Example

DocTestFailure.got

示例的实际输出。

exception doctest.UnexpectedException(test, example, exc_info)

DocTestRunner 引发的异常,表示 doctest 示例引发了意外的异常。构造函数参数用于初始化同名属性。

UnexpectedException 定义了以下属性

UnexpectedException.test

示例失败时正在运行的 DocTest 对象。

UnexpectedException.example

失败的 Example

UnexpectedException.exc_info

一个包含有关意外异常信息的元组,由 sys.exc_info() 返回。

Soapbox

如引言中所述,doctest 已发展出三种主要用途

  1. 检查文档字符串中的示例。

  2. 回归测试。

  3. 可执行文档/文学测试。

这些用途有不同的要求,区分它们很重要。特别是,用晦涩的测试用例填充文档字符串会产生糟糕的文档。

在编写文档字符串时,请谨慎选择文档字符串示例。这是一门需要学习的艺术——起初可能不自然。示例应该为文档增加真正的价值。一个好的示例通常胜过千言万语。如果做得仔细,这些示例对您的用户将是无价之宝,并且随着岁月流逝和事物变化,将无数倍地回报您收集它们所花费的时间。我仍然惊讶于我的一个 doctest 示例在一次“无害”的更改后停止工作的频率。

Doctest 也是一个出色的回归测试工具,尤其是如果您不吝啬解释性文本。通过将散文和示例交织在一起,跟踪实际测试的内容和原因变得容易得多。当测试失败时,好的散文可以使找出问题所在以及如何修复它变得容易得多。诚然,您可以在基于代码的测试中编写大量注释,但很少有程序员这样做。许多人发现,使用 doctest 方法可以产生更清晰的测试。也许这仅仅是因为 doctest 使编写散文比编写代码更容易一些,而在代码中编写注释则更难一些。我认为这比这更深入:编写基于 doctest 的测试时的自然态度是,您想要解释软件的精妙之处,并用示例来说明它们。这反过来又自然地导致测试文件从最简单的功能开始,然后逻辑地进展到复杂性和边缘情况。结果是一个连贯的叙述,而不是一个似乎随机测试孤立功能位的孤立函数集合。这是一种不同的态度,产生不同的结果,模糊了测试和解释之间的区别。

回归测试最好限制在专用的对象或文件中。组织测试有几种选择

  • 编写包含交互式示例的测试用例文本文件,并使用 testfile()DocFileSuite() 测试文件。推荐这样做,尽管对于从一开始就设计为使用 doctest 的新项目来说最容易。

  • 定义名为 _regrtest_topic 的函数,这些函数由单个文档字符串组成,其中包含命名主题的测试用例。这些函数可以包含在与模块相同的文件中,也可以分离到单独的测试文件中。

  • 定义一个 __test__ 字典,将回归测试主题映射到包含测试用例的文档字符串。

当您将测试放在模块中时,该模块本身可以成为测试运行器。当测试失败时,您可以安排测试运行器在调试问题时仅重新运行失败的 doctest。这是一个此类测试运行器的最小示例

if __name__ == '__main__':
    import doctest
    flags = doctest.REPORT_NDIFF|doctest.FAIL_FAST
    if len(sys.argv) > 1:
        name = sys.argv[1]
        if name in globals():
            obj = globals()[name]
        else:
            obj = __test__[name]
        doctest.run_docstring_examples(obj, globals(), name=name,
                                       optionflags=flags)
    else:
        fail, total = doctest.testmod(optionflags=flags)
        print(f"{fail} failures out of {total} tests")

脚注