doctest — 测试交互式 Python 示例 — Python 文档
doctest — 测试交互式 Python 示例
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.py
,doctest 会发挥它的魔力:
$ python example.py
$
没有输出! 这是正常的,这意味着所有示例都有效。 将 -v
传递给脚本,doctest 打印出它正在尝试的详细日志,并在最后打印一个摘要:
$ 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 tests in __main__
8 tests in __main__.factorial
9 tests in 2 items.
9 passed and 0 failed.
Test passed.
$
这就是开始有效使用 doctest 所需的全部知识! 跳进去。 以下部分提供了完整的详细信息。 请注意,标准 Python 测试套件和库中有许多 doctest 示例。 特别有用的示例可以在标准测试文件 Lib/test/test_doctest.py
中找到。
简单用法:检查文档字符串中的示例
开始使用 doctest 的最简单方法(但不一定是您将继续使用的方式)是在每个模块 M
结束时使用:
if __name__ == "__main__":
import doctest
doctest.testmod()
doctest 然后检查模块 M
中的文档字符串。
将模块作为脚本运行会导致文档字符串中的示例被执行和验证:
python M.py
除非示例失败,否则不会显示任何内容,在这种情况下,失败的示例和失败的原因将打印到标准输出,输出的最后一行是 ***Test Failed*** N failures.
,其中 N 是失败的示例数量。
使用 -v
开关运行它:
python M.py -v
将所有尝试过的示例的详细报告打印到标准输出,并在最后打印各种摘要。
您可以通过将 verbose=True
传递给 testmod() 来强制详细模式,或者通过传递 verbose=False
来禁止它。 在任何一种情况下,sys.argv
都不会被 testmod() 检查(因此通过 -v
没有影响)。
还有一个用于运行 testmod() 的命令行快捷方式。 您可以指示 Python 解释器直接从标准库运行 doctest 模块并在命令行上传递模块名称:
python -m doctest -v example.py
这将导入 example.py
作为独立模块并在其上运行 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() 在调用模块的目录中查找文件。 有关可用于告诉它在其他位置查找文件的可选参数的说明,请参阅 Basic API 部分。
与 testmod() 一样,testfile() 的详细程度可以使用 -v
命令行开关或可选关键字参数 verbose 设置]。
还有一个用于运行 testfile() 的命令行快捷方式。 您可以指示 Python 解释器直接从标准库运行 doctest 模块并在命令行上传递文件名:
python -m doctest -v example.txt
因为文件名不以 .py
结尾,所以 doctest 推断它必须与 testfile() 一起运行,而不是 testmod()。
有关 testfile() 的更多信息,请参阅 基本 API 部分。
这个怎么运作
本节详细检查 doctest 的工作原理:它查看哪些文档字符串、它如何查找交互式示例、它使用什么执行上下文、它如何处理异常以及如何使用选项标志来控制其行为。 这是编写 doctest 示例需要了解的信息; 有关在这些示例上实际运行 doctest 的信息,请参阅以下部分。
检查哪些文档字符串?
搜索模块文档字符串以及所有函数、类和方法文档字符串。 不搜索导入模块的对象。
此外,如果 M.__test__
存在且“为真”,则它必须是一个 dict,并且每个条目将一个(字符串)名称映射到一个函数对象、类对象或字符串。 搜索从 M.__test__
找到的函数和类对象文档字符串,并将字符串视为文档字符串。 在输出中,M.__test__
中的一个键 K
与名称一起出现
<name of M>.__test__.K
找到的任何类都以类似的方式递归搜索,以测试其包含的方法和嵌套类中的文档字符串。
如何识别文档字符串示例?
在大多数情况下,交互式控制台会话的复制和粘贴工作正常,但 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!!!
>>>
任何预期输出必须紧跟在包含代码的最后 '>>> '
或 '... '
行之后,并且预期输出(如果有)延伸到下一个 '>>> '
或全空白行。
精美的印刷品:
预期输出不能包含全空白行,因为这样的行被用来表示预期输出的结束。 如果预期的输出确实包含一个空行,请将
<BLANKLINE>
放在您的 doctest 示例中,每个地方都需要一个空行。使用 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
中顶级定义的任何名称,以及在运行的文档字符串中先前定义的名称。 示例无法看到其他文档字符串中定义的名称。
您可以通过将 globs=your_dict
传递给 testmod() 或 testfile() 来强制使用自己的 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 被引发,该 doctest 会成功,list.remove(x): x not in list
细节如图所示。
异常的预期输出必须以回溯标头开头,它可以是以下两行之一,缩进与示例的第一行相同:
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 选项无关。 该示例中的省略号可以省略,也可以是三个(或三百个)逗号或数字,或者是巨蟒短剧的缩进抄本。
一些细节你应该读一次,但不需要记住:
- Doctest 无法猜测您的预期输出是来自异常回溯还是来自普通打印。 因此,例如,一个期望
ValueError: 42 is prime
的示例将通过 ValueError 是否实际引发,或者该示例是否仅打印回溯文本。 实际上,普通输出很少以回溯标题行开头,因此这不会产生真正的问题。 - 回溯堆栈的每一行(如果存在)必须比示例的第一行缩进得更远, 或 以非字母数字字符开头。 回溯标头后面的第一行缩进相同并以字母数字开头被视为异常详细信息的开始。 当然,这对于真正的回溯来说是正确的。
- 当指定 IGNORE_EXCEPTION_DETAIL doctest 选项时,最左边冒号后面的所有内容和异常名称中的任何模块信息都将被忽略。
- 交互式 shell 省略了某些 SyntaxError 的回溯标题行。 但是 doctest 使用回溯标题行来区分异常和非异常。 因此,在需要测试省略回溯标头的 SyntaxError 的罕见情况下,您需要手动将回溯标头行添加到测试示例中。
对于某些 SyntaxError,Python 使用
^
标记显示语法错误的字符位置:>>> 1 1 File "<stdin>", line 1 1 1 ^ SyntaxError: invalid syntax
由于显示错误位置的行出现在异常类型和详细信息之前,因此 doctest 不会检查它们。 例如,下面的测试会通过,即使它把
^
标记放在错误的位置:>>> 1 1 File "<stdin>", line 1 1 1 ^ SyntaxError: invalid syntax
选项标志
许多选项标志控制 doctest 行为的各个方面。 标志的符号名称作为模块常量提供,它们可以 按位或 一起传递给各种函数。 这些名称也可以在 doctest 指令 中使用,并且可以通过 -o
选项传递给 doctest 命令行界面。
3.4 新功能:-o
命令行选项。
第一组选项定义测试语义,控制 doctest 如何决定实际输出是否与示例的预期输出匹配:
- doctest.DONT_ACCEPT_TRUE_FOR_1
- 默认情况下,如果预期输出块仅包含
1
,则仅包含1
或仅包含True
的实际输出块被视为匹配,对于0
与False
。 当指定 DONT_ACCEPT_TRUE_FOR_1 时,不允许任何替换。 默认行为迎合了 Python 将许多函数的返回类型从整数更改为布尔值的情况; 在这些情况下,期望“小整数”输出的 doctests 仍然有效。 此选项可能会消失,但不会持续数年。
- doctest.DONT_ACCEPT_BLANKLINE
- 默认情况下,如果预期输出块包含仅包含字符串
<BLANKLINE>
的行,则该行将匹配实际输出中的空行。 因为真正的空行分隔了预期的输出,所以这是传达预期有空行的唯一方法。 当指定 DONT_ACCEPT_BLANKLINE 时,不允许这种替换。
- doctest.NORMALIZE_WHITESPACE
- 指定后,所有空格序列(空格和换行符)都被视为相等。 预期输出中的任何空格序列都将匹配实际输出中的任何空格序列。 默认情况下,空格必须完全匹配。 NORMALIZE_WHITESPACE 当预期输出的一行很长,并且您想将它包装在源代码中的多行时特别有用。
- doctest.ELLIPSIS
- 指定后,预期输出中的省略号标记 (
...
) 可以匹配实际输出中的任何子字符串。 这包括跨越行边界的子字符串和空子字符串,因此最好保持这种简单的用法。 复杂的使用会导致相同类型的“哎呀,太匹配了!” 令人惊讶的是.*
在正则表达式中很容易出现。
- doctest.IGNORE_EXCEPTION_DETAIL
指定后,如果引发预期类型的异常,即使异常详细信息不匹配,也会通过预期异常的示例。 例如,如果实际引发的异常为
ValueError: 3*14
,则期望ValueError: 42
的示例将通过,但会失败,例如,如果引发 TypeError。它还将忽略 Python 3 doctest 报告中使用的模块名称。 因此,无论测试是在 Python 2.7 还是 Python 3.2(或更高版本)下运行,这两种变体都将使用指定的标志:
>>> raise CustomError('message') Traceback (most recent call last): CustomError: message >>> raise CustomError('message') Traceback (most recent call last): my_module.CustomError: message
请注意, ELLIPSIS 也可用于忽略异常消息的详细信息,但根据模块详细信息是否作为异常名称的一部分打印,此类测试仍可能会失败。 使用 IGNORE_EXCEPTION_DETAIL 和 Python 2.3 的详细信息也是编写不关心异常详细信息但继续在 Python 2.3 或更早版本下传递的 doctest 的唯一明确方法(这些版本不支持 [X243X ]doctest 指令 并将它们作为不相关的注释忽略)。 例如:
>>> (1, 2)[3] = 'moo' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: object doesn't support item assignment
在带有指定标志的 Python 2.3 和更高版本的 Python 下通过,即使在 Python 2.4 中细节更改为“不”而不是“不”。
3.2 版更改:IGNORE_EXCEPTION_DETAIL 现在也忽略与包含被测异常的模块相关的任何信息。
- doctest.SKIP
指定时,根本不运行示例。 这在 doctest 示例既用作文档又用作测试用例的上下文中很有用,并且应该包含一个示例用于文档目的,但不应检查。 例如,示例的输出可能是随机的; 或者示例可能依赖于测试驱动程序不可用的资源。
SKIP 标志也可用于临时“注释掉”示例。
- doctest.COMPARISON_FLAGS
- 位掩码或将上面的所有比较标志组合在一起。
第二组选项控制如何报告测试失败:
- doctest.REPORT_UDIFF
- 指定时,使用统一差异显示涉及多行预期和实际输出的故障。
- doctest.REPORT_CDIFF
- 指定后,将使用上下文差异显示涉及多行预期和实际输出的故障。
- 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 命令行接受选项
-f
作为-o FAIL_FAST
的简写。3.4 版中的新功能。
- doctest.REPORTING_FLAGS
- 位掩码或将上述所有报告标志组合在一起。
还有一种方法可以注册新的选项标志名称,尽管这没有用,除非您打算通过子类化扩展 doctest 内部:
- doctest.register_optionflag(name)
创建具有给定名称的新选项标志,并返回新标志的整数值。 register_optionflag() 可用于子类化 OutputChecker 或 DocTestRunner 以创建子类支持的新选项。 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)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
如果没有该指令,它将失败,这既是因为实际输出在一位数列表元素之前没有两个空格,也因为实际输出在一行上。 这个测试也通过了,并且还需要一个指令来这样做:
>>> print(list(range(20)))
[0, 1, ..., 18, 19]
可以在单个物理行上使用多个指令,用逗号分隔:
>>> print(list(range(20)))
[0, 1, ..., 18, 19]
如果多个指令注释用于单个示例,则将它们组合在一起:
>>> print(list(range(20)))
...
[0, 1, ..., 18, 19]
如前面的示例所示,您可以将 ...
行添加到仅包含指令的示例中。 当示例太长以至于指令无法舒适地放在同一行中时,这可能很有用:
>>> print(list(range(5)) + list(range(10, 20)) + list(range(30, 40)))
...
[0, ..., 4, 10, ..., 19, 30, ..., 39]
请注意,由于默认情况下所有选项都是禁用的,并且指令仅适用于它们出现的示例,因此启用选项(通过指令中的 +
)通常是唯一有意义的选择。 但是,选项标志也可以传递给运行 doctests 的函数,建立不同的默认值。 在这种情况下,通过指令中的 -
禁用选项会很有用。
警告
doctest 认真对待要求在预期输出中完全匹配。 如果单个字符不匹配,则测试失败。 这可能会让您感到惊讶几次,因为您确切地了解了 Python 的功能和不保证输出的内容。 例如,在打印集合时,Python 不保证元素以任何特定顺序打印,因此像这样的测试
>>> foo()
{"Hermione", "Harry"}
很脆弱! 一种解决方法是做
>>> foo() == {"Hermione", "Harry"}
True
反而。 另一个是做
>>> d = sorted(foo())
>>> d
['Harry', 'Hermione']
笔记
在 Python 3.6 之前,在打印 dict 时,Python 不保证键值对按任何特定顺序打印。
还有其他的,但你懂的。
另一个坏主意是打印嵌入对象地址的东西,比如
>>> id(1.0) # certain to fail some of the time
7948648
>>> class C: pass
>>> C() # the default repr() for instances embeds an address
<__main__.C instance at 0x00AC18F0>
ELLIPSIS 指令为最后一个例子提供了一个很好的方法:
>>> C()
<__main__.C instance at 0x...>
浮点数也受制于跨平台的小输出变化,因为 Python 遵循平台 C 库进行浮点格式化,而 C 库在此处的质量差异很大。
>>> 1./7 # risky
0.14285714285714285
>>> print(1./7) # safer
0.142857142857
>>> print(round(1./7, 6)) # much safer
0.142857
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 创建了这个 dict 的一个新的浅拷贝,所以它的例子从一个干净的石板开始。 默认情况下,或者如果
None
,则使用新的空字典。可选参数 extraglobs 给出合并到用于执行示例的全局变量中的字典。 这类似于 dict.update():如果 globs 和 extraglobs 有一个公共键,则 extraglobs 中的关联值出现在组合字典。 默认情况下,或者如果
None
,不使用额外的全局变量。 这是一项允许对 doctest 进行参数化的高级功能。 例如,可以为基类编写 doctest,使用类的通用名称,然后通过传递 extraglobs dict 将通用名称映射到要测试的子类来重用以测试任意数量的子类.可选参数 verbose 如果为真则打印大量内容,如果为假则仅打印失败; 默认情况下,或者如果
None
,当且仅当'-v'
在sys.argv
中时为真。可选参数 report 为真时在末尾打印摘要,否则在末尾不打印任何内容。 在详细模式下,摘要是详细的,否则摘要非常简短(实际上,如果所有测试都通过,则为空)。
可选参数 optionflags(默认值 0)采用选项标志的 按位 OR。 请参阅部分 选项标志 。
可选参数 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__
。还可以从 dict
m.__test__
访问测试示例,如果它存在并且不是None
。m.__test__
将名称(字符串)映射到函数、类和字符串; 搜索函数和类文档字符串以获取示例; 直接搜索字符串,就好像它们是文档字符串一样。仅搜索附加到属于模块 m 的对象的文档字符串。
返回
(failure_count, test_count)
。可选参数 name 给出模块的名称; 默认情况下,或者如果使用
None
,则使用m.__name__
。可选参数 exclude_empty 默认为 false。 如果为 true,则将没有找到 doctest 的对象排除在考虑范围之外。 默认是向后兼容的 hack,所以仍然使用
doctest.master.summarize()
和 testmod() 的代码继续获得没有测试的对象的输出。 较新的 DocTestFinder 构造函数的 exclude_empty 参数默认为 true。可选参数 extraglobs、verbose、report、optionflags、raise_on_error 和 globs[X12135X]与上面的函数 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() 一样工作。
单元测试 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
从文本文件和带有 doctests 的模块创建 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 对象。 setUp 函数可以访问测试全局变量作为测试通过的 globs 属性。
可选参数 globs 是一个包含测试初始全局变量的字典。 为每个测试创建该词典的新副本。 默认情况下, globs 是一个新的空字典。
可选参数 optionflags 指定测试的默认 doctest 选项,通过或将单个选项标志组合在一起创建。 请参阅部分 选项标志 。 有关设置报告选项的更好方法,请参阅下面的函数 set_unittest_reportflags()。
可选参数 parser 指定应用于从文件中提取测试的 DocTestParser(或子类)。 它默认为普通解析器(即
DocTestParser()
)。可选参数 encoding 指定用于将文件转换为 unicode 的编码。
全局
__file__
被添加到提供给使用 DocFileSuite() 从文本文件加载的 doctests 的全局变量中。
- doctest.DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, setUp=None, tearDown=None, checker=None)
将模块的 doctest 测试转换为 unittest.TestSuite。
返回的 unittest.TestSuite 将由 unittest 框架运行,并运行模块中的每个 doctest。 如果任何 doctests 失败,则合成单元测试失败,并引发
failureException
异常,显示包含测试的文件的名称和(有时是近似的)行号。可选参数 module 提供要测试的模块。 它可以是模块对象或(可能是虚线)模块名称。 如果未指定,则使用调用此函数的模块。
可选参数 globs 是一个包含测试初始全局变量的字典。 为每个测试创建该词典的新副本。 默认情况下, globs 是一个新的空字典。
可选参数 extraglobs 指定一组额外的全局变量,它被合并到 globs 中。 默认情况下,不使用额外的全局变量。
可选参数 test_finder 是用于从模块中提取 doctests 的 DocTestFinder 对象(或替代品)。
可选参数 setUp、tearDown 和 optionflags 与上述函数 DocFileSuite() 相同。
此函数使用与 testmod() 相同的搜索技术。
在 3.5 版更改:DocTestSuite() 返回一个空的 unittest.TestSuite 如果 module 不包含文档字符串而不是引发 ValueError 。
在幕后,DocTestSuite() 从 doctest.DocTestCase
实例中创建了一个 unittest.TestSuite,而 DocTestCase
是 unittest 的子类。测试用例。 DocTestCase
未在此处记录(这是一个内部细节),但研究其代码可以回答有关 unittest 集成的确切细节的问题。
类似地,DocFileSuite() 从 doctest.DocFileCase
实例中创建一个 unittest.TestSuite,而 DocFileCase
是 DocTestCase
的子类。
因此,创建 unittest.TestSuite 的两种方法都会运行 DocTestCase
的实例。 这很重要,原因很微妙:当您自己运行 doctest 函数时,您可以通过将选项标志传递给 doctest 函数来直接控制正在使用的 doctest 选项. 但是,如果您正在编写 unittest 框架,则 unittest 最终控制测试何时以及如何运行。 框架作者通常希望控制 doctest 报告选项(也许,例如,由命令行选项指定),但无法通过 unittest 将选项传递给 doctest ] 测试运行器。
出于这个原因,doctest 还支持 doctest 报告标志的概念,特定于 unittest 支持,通过这个函数:
- doctest.set_unittest_reportflags(flags)
设置要使用的 doctest 报告标志。
参数 flags 采用选项标志的 按位 OR。 请参阅部分 选项标志 。 只能使用“报告标志”。
这是一个模块全局设置,并影响由模块 unittest 运行的所有未来 doctest:
DocTestCase
的runTest()
方法查看为测试用例指定的选项标志,当DocTestCase
实例已构建。 如果没有指定报告标志(这是典型的和预期的情况),doctest 的 unittest 报告标志是 按位 ORed 进入选项标志,并且如此增加的选项标志被传递给为运行 doctest 而创建的 DocTestRunner 实例。 如果在构造DocTestCase
实例时指定了任何报告标志,则忽略 doctest 的 unittest 报告标志。函数返回在调用函数之前有效的 unittest 报告标志的值。
高级API
基本 API 是一个简单的包装器,旨在使 doctest 易于使用。 它相当灵活,应该可以满足大多数用户的需求; 但是,如果您需要对测试进行更细粒度的控制,或者希望扩展 doctest 的功能,那么您应该使用高级 API。
高级 API 围绕两个容器类,用于存储从 doctest 案例中提取的交互式示例:
定义了额外的处理类来查找、解析、运行和检查 doctest 示例:
- DocTestFinder:查找给定模块中的所有文档字符串,并使用 DocTestParser 从包含交互式示例的每个文档字符串创建 DocTest。
- DocTestParser:从字符串(例如对象的文档字符串)创建一个 DocTest 对象。
- DocTestRunner:执行 DocTest 中的示例,并使用 OutputChecker 验证其输出。
- OutputChecker:将来自 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 的字符串名称。 通常,这是从中提取测试的对象或文件的名称。
- docstring
从中提取测试的字符串,或者
None
如果字符串不可用,或者测试不是从字符串中提取的。
示例对象
- class doctest.Example(source, want, exc_msg=None, lineno=0, indent=0, options=None)
单个交互式示例,由 Python 语句及其预期输出组成。 构造函数参数用于初始化相同名称的属性。
示例 定义了以下属性。 它们由构造函数初始化,不应直接修改。
- source
包含示例源代码的字符串。 此源代码由单个 Python 语句组成,并始终以换行符结尾; 构造函数在必要时添加一个换行符。
- want
运行示例源代码的预期输出(来自标准输出,或出现异常时的回溯)。 want 以换行符结束,除非预期没有输出,在这种情况下它是一个空字符串。 构造函数在必要时添加一个换行符。
- exc_msg
示例生成的异常消息,如果示例预计会产生异常; 或
None
如果预计不会产生异常。 将此异常消息与 traceback.format_exception_only() 的返回值进行比较。 exc_msg 以换行符结束,除非它是None
。 如果需要,构造函数会添加一个换行符。
- lineno
包含此示例的字符串中示例开始处的行号。 该行号相对于包含字符串的开头是从零开始的。
- indent
示例在包含字符串中的缩进,即示例第一个提示之前的空格字符数。
- options
从选项标志到
True
或False
的字典映射,用于覆盖此示例的默认选项。 未包含在此字典中的任何选项标志都保留其默认值(由 DocTestRunner 的optionflags
指定)。 默认情况下,未设置任何选项。
DocTestFinder 对象
- class doctest.DocTestFinder(verbose=False, parser=DocTestParser(), recurse=True, exclude_empty=True)
用于从给定对象的文档字符串及其包含对象的文档字符串中提取与给定对象相关的 DocTest 的处理类。 DocTests 可以从模块、类、函数、方法、静态方法、类方法和属性中提取。
可选参数 verbose 可用于显示查找器搜索的对象。 默认为
False
(无输出)。可选参数 parser 指定用于从文档字符串中提取文档测试的 DocTestParser 对象(或替代品)。
如果可选参数 recurse 为 false,则 DocTestFinder.find() 将只检查给定的对象,而不检查任何包含的对象。
如果可选参数 exclude_empty 为 false,则 DocTestFinder.find() 将包含对具有空文档字符串的对象的测试。
DocTestFinder 定义了以下方法:
- find(obj[, name][, module][, globs][, extraglobs])
返回由 obj 的文档字符串或其包含的任何对象的文档字符串定义的 DocTest 的列表。
可选参数 name 指定对象的名称; 此名称将用于为返回的 DocTest 构造名称。 如果未指定 name,则使用
obj.__name__
。可选参数 module 是包含给定对象的模块。 如果模块未指定或为
None
,则测试仪将尝试自动确定正确的模块。 使用对象的模块:作为默认命名空间,如果未指定 globs。
防止 DocTestFinder 从从其他模块导入的对象中提取 DocTest。 (具有除 module 以外的模块的包含对象将被忽略。)
查找包含对象的文件的名称。
帮助查找对象在其文件中的行号。
如果 module 是
False
,则不会尝试查找模块。 这很模糊,主要用于测试 doctest 本身:如果 module 是False
,或者是None
但不能自动找到,那么所有对象都被认为属于(不存在的)模块,因此将(递归地)搜索所有包含的对象以查找 doctest。每个 DocTest 的全局变量是通过组合 globs 和 extraglobs 形成的(extraglobs 中的绑定覆盖 globs 中的绑定)。 为每个 DocTest 创建一个 globals 字典的新浅表副本。 如果未指定 globs,则默认为模块的 __dict__(如果指定),否则为
{}
。 如果未指定 extraglobs,则默认为{}
。
DocTestParser 对象
- class doctest.DocTestParser
用于从字符串中提取交互式示例并使用它们创建 DocTest 对象的处理类。
DocTestParser 定义了以下方法:
- get_doctest(string, globs, name, filename, lineno)
从给定的字符串中提取所有 doctest 示例,并将它们收集到 DocTest 对象中。
globs、name、filename 和 lineno 是新的 DocTest 对象的属性。 有关更多信息,请参阅 DocTest 的文档。
- get_examples(string, name='<string>')
从给定的字符串中提取所有 doctest 示例,并将它们作为 Example 对象的列表返回。 行号从 0 开始。 可选参数 name 是标识此字符串的名称,仅用于错误消息。
DocTestRunner 对象
- class doctest.DocTestRunner(checker=None, verbose=None, optionflags=0)
用于执行和验证 DocTest 中的交互式示例的处理类。
预期输出和实际输出之间的比较由 OutputChecker 完成。 可以使用许多选项标志来自定义此比较; 有关更多信息,请参阅 选项标志 部分。 如果选项标志不足,则还可以通过将 OutputChecker 的子类传递给构造函数来自定义比较。
可以通过两种方式控制测试运行器的显示输出。 首先,可以将一个输出函数传递给
TestRunner.run()
; 将使用应显示的字符串调用此函数。 默认为sys.stdout.write
。 如果捕获输出不够,那么也可以通过子类化 DocTestRunner 来自定义显示输出,并覆盖方法 report_start(), report_success(), report_unexpected_exception() 和 report_failure()。可选的关键字参数 checker 指定了 OutputChecker 对象(或直接替换),该对象应该用于将预期输出与 doctest 示例的实际输出进行比较。
可选的关键字参数 verbose 控制 DocTestRunner 的详细程度。 如果 verbose 是
True
,则在运行时打印每个示例的信息。 如果 verbose 是False
,则只打印失败。 如果未指定 verbose 或None
,则在使用命令行开关-v
的情况下使用详细输出。可选关键字参数 optionflags 可用于控制测试运行器如何将预期输出与实际输出进行比较,以及如何显示失败。 有关详细信息,请参阅 选项标志 部分。
DocTestParser 定义了以下方法:
- 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 显示结果。
这些示例在命名空间
test.globs
中运行。 如果 clear_globs 为 true(默认值),那么这个命名空间将在测试运行后被清除,以帮助垃圾收集。 如果您想在测试完成后检查命名空间,请使用 clear_globs=False。compileflags 给出了 Python 编译器在运行示例时应该使用的标志集。 如果未指定,则默认为适用于 globs 的一组未来导入标志。
使用 DocTestRunner 的输出检查器检查每个示例的输出,结果由
DocTestRunner.report_*()
方法格式化。
- summarize(verbose=None)
打印此 DocTestRunner 已运行的所有测试用例的摘要,并返回一个名为 的元组
TestResults(failed, attempted)
。可选的 verbose 参数控制摘要的详细程度。 如果未指定详细程度,则使用 DocTestRunner 的详细程度。
输出检查器对象
- 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 是用于比较 want 和 got 的选项标志集。
调试
Doctest 提供了几种调试 doctest 示例的机制:
几个函数将 doctests 转换为可执行的 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) >>>
将 doctests 转换为 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 是一个模块对象,或一个模块的带点名称,包含其 doctests 感兴趣的对象。 参数 name 是具有感兴趣的 doctest 的对象的名称(在模块内)。 结果是一个字符串,其中包含转换为 Python 脚本的对象文档字符串,如上文 script_from_examples() 所述。 例如,如果模块
a.py
包含一个顶级函数f()
,则import a, doctest print(doctest.testsource(a, "a.f"))
打印函数
f()
的文档字符串的脚本版本,将文档测试转换为代码,其余放在注释中。
- doctest.debug(module, name, pm=False)
调试对象的文档测试。
module 和 name 参数与上面的函数 testsource() 相同。 命名对象的文档字符串的合成 Python 脚本被写入一个临时文件,然后该文件在 Python 调试器 pdb 的控制下运行。
module.__dict__
的浅拷贝用于本地和全局执行上下文。可选参数 pm 控制是否使用事后调试。 如果 pm 为真值,则直接运行脚本文件,并且仅当脚本通过引发未处理的异常终止时才涉及调试器。 如果是,则通过 pdb.post_mortem() 调用事后调试,传递来自未处理异常的回溯对象。 如果未指定 pm 或为 false,则脚本从一开始就在调试器下运行,通过将适当的 exec() 调用传递给 pdb.run() 。
- doctest.debug_src(src, pm=False, globs=None)
调试字符串中的文档测试。
这类似于上面的函数 debug(),不同之处在于通过 src 参数直接指定包含 doctest 示例的字符串。
可选参数 pm 与上面的函数 debug() 具有相同的含义。
可选参数 globs 给出一个字典,用作本地和全局执行上下文。 如果未指定,或
None
,则使用空字典。 如果指定,则使用字典的浅拷贝。
DebugRunner 类,以及它可能引发的特殊异常,是测试框架作者最感兴趣的,这里仅作简要介绍。 查看源代码,尤其是 DebugRunner 的 docstring(这是一个 doctest!)以获取更多详细信息:
- class doctest.DebugRunner(checker=None, verbose=None, optionflags=0)
DocTestRunner 的子类,一旦遇到故障就会引发异常。 如果发生意外异常,则会引发 UnexpectedException 异常,其中包含测试、示例和原始异常。 如果输出不匹配,则会引发 DocTestFailure 异常,其中包含测试、示例和实际输出。
有关构造函数参数和方法的信息,请参阅 Advanced API 部分中的 DocTestRunner 文档。
DebugRunner 实例可能会引发两个异常:
- exception doctest.DocTestFailure(test, example, got)
- DocTestRunner 引发的异常表示 doctest 示例的实际输出与其预期输出不匹配。 构造函数参数用于初始化相同名称的属性。
DocTestFailure 定义了以下属性:
- DocTestFailure.test
- 示例失败时正在运行的 DocTest 对象。
- DocTestFailure.example
- 失败的 示例 。
- DocTestFailure.got
- 该示例的实际输出。
- exception doctest.UnexpectedException(test, example, exc_info)
- DocTestRunner 引发的异常,表示 doctest 示例引发了意外异常。 构造函数参数用于初始化相同名称的属性。
UnexpectedException 定义了以下属性:
- UnexpectedException.test
- 示例失败时正在运行的 DocTest 对象。
- UnexpectedException.example
- 失败的 示例 。
- UnexpectedException.exc_info
- 包含有关意外异常的信息的元组,由 sys.exc_info() 返回。
肥皂盒
正如介绍中提到的,doctest 已经成长为三个主要用途:
- 检查文档字符串中的示例。
- 回归测试。
- 可执行文件/文字测试。
这些用途有不同的要求,区分它们很重要。 特别是,用晦涩的测试用例填充你的文档字符串会导致糟糕的文档。
编写文档字符串时,请谨慎选择文档字符串示例。 有一种艺术需要学习 - 一开始可能并不自然。 示例应该为文档增加真正的价值。 一个好的例子往往值得说很多话。 如果小心完成,这些示例对您的用户来说将是无价的,并且随着时间的流逝和事情的变化,将回报多次收集它们所需的时间。 我仍然对我的 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("{} failures out of {} tests".format(fail, total))
脚注
- 1
- 不支持同时包含预期输出和异常的示例。 试图猜测一个结束和另一个开始的位置太容易出错,而且这也使测试变得混乱。