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.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 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
或不传递都没有效果)。
简单用法:检查文本文件中的示例¶
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
- -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
的实际输出块被认为是匹配的,同样适用于0
与False
。当指定了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()
可以在子类化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))) # 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
对象(或其替代品)。可选参数 setUp、tearDown 和 optionflags 与上面的函数
DocFileSuite()
相同,但它们是为每个文档字符串调用的。此函数使用与
testmod()
相同的搜索技术。在 3.5 版更改: 如果 module 不包含文档字符串,
DocTestSuite()
会返回一个空的unittest.TestSuite
,而不是引发ValueError
。
在底层,DocTestSuite()
从 doctest.DocTestCase
实例创建了一个 unittest.TestSuite
,而 DocTestCase
是 unittest.TestCase
的子类。这里没有记录 DocTestCase
(它是一个内部细节),但研究其代码可以回答有关 unittest
集成确切细节的问题。
同样,DocFileSuite()
从 doctest.DocFileCase
实例创建一个 unittest.TestSuite
,而 DocFileCase
是 DocTestCase
的子类。
因此,创建 unittest.TestSuite
的两种方式都运行 DocTestCase
的实例。这有一个微妙的重要原因:当您自己运行 doctest
函数时,可以通过向 doctest
函数传递选项标志来直接控制正在使用的 doctest
选项。但是,如果您正在编写一个 unittest
框架,unittest
最终控制着测试的运行时间和方式。框架作者通常希望控制 doctest
的报告选项(例如,可能由命令行选项指定),但没有办法通过 unittest
将选项传递给 doctest
的测试运行器。
因此,doctest
还通过此函数支持特定于 unittest
支持的 doctest
报告标志的概念
- doctest.set_unittest_reportflags(flags)¶
设置要使用的
doctest
报告标志。参数 flags 接受选项标志的按位或。请参阅 选项标志 部分。只能使用“报告标志”。
这是一个模块全局设置,会影响
unittest
模块运行的所有未来 doctest:DocTestCase
的runTest()
方法会查看构造DocTestCase
实例时为测试用例指定的选项标志。如果未指定报告标志(这是典型和预期的情况),doctest
的unittest
报告标志会按位或到选项标志中,然后将如此增强的选项标志传递给为运行 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 对象¶
Example 对象¶
- class doctest.Example(source, want, exc_msg=None, lineno=0, indent=0, options=None)¶
单个交互式示例,由一个 Python 语句及其预期输出组成。构造函数参数用于初始化同名属性。
Example
定义了以下属性。它们由构造函数初始化,不应直接修改。- source¶
包含示例源代码的字符串。此源代码由单个 Python 语句组成,并始终以换行符结尾;构造函数在必要时会添加换行符。
- 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
s。DocTest
s 可以从模块、类、函数、方法、静态方法、类方法和属性中提取。可选参数 verbose 可用于显示查找器搜索的对象。它默认为
False
(无输出)。可选参数 parser 指定用于从文档字符串中提取 doctest 的
DocTestParser
对象(或其替代品)。如果可选参数 recurse 为 false,则
DocTestFinder.find()
将只检查给定的对象,而不检查任何包含的对象。如果可选参数 exclude_empty 为 false,则
DocTestFinder.find()
将包含文档字符串为空的对象的测试。DocTestFinder
定义了以下方法- find(obj[, name][, module][, globs][, extraglobs])¶
返回由 obj 的文档字符串或其任何包含对象的文档字符串定义的
DocTest
s 列表。可选参数 name 指定对象的名称;此名称将用于构造返回的
DocTest
s 的名称。如果未指定 name,则使用obj.__name__
。可选参数 module 是包含给定对象的模块。如果未指定模块或是
None
,则测试查找器将尝试自动确定正确的模块。对象的模块用于作为默认命名空间,如果未指定 globs。
防止 DocTestFinder 从其他模块导入的对象中提取 DocTests。(模块不同于 module 的包含对象将被忽略。)
查找包含对象的文件名。
帮助在其文件中查找对象的行号。
如果 module 是
False
,则不会尝试查找模块。这很晦涩,主要用于测试 doctest 本身:如果 module 是False
,或是None
但无法自动找到,则所有对象都被认为属于(不存在的)模块,因此将(递归地)搜索所有包含的对象以查找 doctest。每个
DocTest
的全局变量是通过组合 globs 和 extraglobs 形成的(extraglobs 中的绑定会覆盖 globs 中的绑定)。为每个DocTest
创建一个新的全局字典浅拷贝。如果未指定 globs,则默认为模块的__dict__
(如果已指定),否则为{}
。如果未指定 extraglobs,则默认为{}
。
DocTestParser 对象¶
- class doctest.DocTestParser¶
一个处理类,用于从字符串中提取交互式示例,并使用它们创建一个
DocTest
对象。DocTestParser
定义了以下方法- get_doctest(string, globs, name, filename, lineno)¶
从给定字符串中提取所有 doctest 示例,并将它们收集到一个
DocTest
对象中。globs、name、filename 和 lineno 是新的
DocTest
对象的属性。有关更多信息,请参阅DocTest
的文档。
TestResults 对象¶
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
的详细程度。如果 verbose 是True
,则在运行时会打印有关每个示例的信息。如果 verbose 是False
,则只打印失败信息。如果未指定 verbose 或为None
,则仅当使用命令行开关-v
时才使用详细输出。可选关键字参数 optionflags 可用于控制测试运行器如何比较预期输出与实际输出,以及如何显示失败。有关更多信息,请参阅 选项标志 部分。
测试运行器会累积统计信息。尝试、失败和跳过的示例的总数也可以通过
tries
、failures
和skips
属性获得。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 是用于比较 want 和 got 的选项标志集。
调试¶
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。
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)¶
调试字符串中的 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.got¶
示例的实际输出。
- exception doctest.UnexpectedException(test, example, exc_info)¶
由
DocTestRunner
引发的异常,表示 doctest 示例引发了意外的异常。构造函数参数用于初始化同名属性。
UnexpectedException
定义了以下属性
- UnexpectedException.exc_info¶
一个包含有关意外异常信息的元组,由
sys.exc_info()
返回。
Soapbox¶
如引言中所述,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(f"{fail} failures out of {total} tests")
脚注