python 标准库 doctest
doctest
模块搜索看起来像交互式Python会话的文本片段,然后执行这些会话以验证它们完全按照所示方式工作。有几种常用的方法可以使用doctest:
通过验证所有交互式示例仍按记录工作来检查模块的文档是否是最新的。
通过验证测试文件或测试对象中的交互式示例如预期工作来执行回归测试。
为包编写教程文档,用输入输出示例大量地说明。根据实例或说明文本是否被强调,这具有“识字测试”或“可执行文档”的味道。
这是一个完整但很小的示例模块:
1 | """ |
如果您直接从命令行运行example.py
,那么doctest
可以发挥它的魔力:
1 | $ python example.py |
没有输出!这很正常,而且这意味着所有的例子都有效。传递-v
给脚本,并doctest
打印它正在尝试的详细日志,并在最后打印摘要:
1 | $ python example.py -v |
以此类推,最终结局如下:
1 | Trying: |
这就是你需要知道的开始有效使用doctest
!跳入。以下部分提供完整的详细信息。请注意,标准Python测试套件和库中有很多文档测试的例子。在标准测试文件中可以找到特别有用的示例Lib/test/test_doctest.py
。
1.简单用法:检查Docstrings中的示例
开始使用doctest的最简单的方法(但不一定是您继续这样做的方式)是结束每个模块M
:
1 | if __name__ == "__main__": |
然后doctest
在模块中检查文档字符串M
。
将模块作为脚本运行会导致文档字符串中的示例得到执行和验证:
1 | python M.py |
这将不会显示任何东西,除非一个例子失败,在这种情况下,失败的例子和失败的原因被打印到标准输出,并且输出的最后一行是***Test Failed*** N failures.
,其中N是数字失败的例子。
改为使用开关运行-v
:
1 | python M.py -v |
并且所有尝试过的示例的详细报告都会打印到标准输出,并在最后列出各种摘要。
您可以通过传递verbose=True
来强制详细模式testmod()
,或通过传递来禁止它verbose=False
。在任何一种情况下,sys.argv
都不会被检查testmod()
(如此通过-v
或没有影响)。
自Python 2.6以来,还有一个用于运行的命令行快捷方式testmod()
。您可以指示Python解释器直接从标准库运行doctest模块,并在命令行上传递模块名称:
1 | python -m doctest -v example.py |
这将example.py
作为独立模块导入并testmod()
在其上运行。请注意,如果文件是软件包的一部分并从该软件包导入其他子模块,则可能无法正常工作。
有关更多信息testmod()
,请参阅基本API一节。
2.简单的用法:检查文本文件中的示例
doctest的另一个简单应用是在文本文件中测试交互式示例。这可以通过以下testfile()
功能完成:
1 | import doctest |
该短脚本执行并验证文件中包含的任何交互式Python示例example.txt
。文件内容被视为一个巨大的文档字符串; 该文件不需要包含Python程序!例如,也许example.txt
包含这个:
1 | The ``example`` module |
运行doctest.testfile("example.txt")
然后在这个文档中找到错误:
1 | File "./example.txt", line 14, in example.txt |
与testmod()
一样,testfile()
除非例子失败,否则不会显示任何内容。如果一个例子失败了,那么失败的例子和失败的原因将被打印到标准输出中,格式为testmod()
。
默认情况下,testfile()
查找调用模块目录中的文件。有关可用于指示其在其他位置查找文件的可选参数的说明,请参见Basic API一节。
就像testmod()
,testfile()
可以通过-v
命令行开关或可选的关键字参数verbose来设置详细程度。
自Python 2.6以来,还有一个用于运行的命令行快捷方式testfile()
。您可以指示Python解释器直接从标准库运行doctest模块,并在命令行上传递文件名:
1 | python -m doctest -v example.txt |
因为文件名并不以此结束.py
,因此doctest
推断它必须与其一起运行testfile()
,而不是testmod()
。
有关更多信息testfile()
,请参阅基本API一节。
3.它是如何工作的
本节将详细介绍doctest如何工作:查看它的文档字符串,它如何查找交互式示例,它使用的执行上下文,它如何处理异常以及如何使用选项标志来控制其行为。这是编写doctest示例时需要了解的信息; 有关在这些示例上实际运行doctest的信息,请参阅以下各节。
3.1. 哪些Docstrings被检查?
模块docstring,以及所有函数,类和方法文档字符串被搜索。导入到模块中的对象不被搜索。
另外,如果M.__test__
存在且“为真”,则它必须是字典,并且每个条目将(字符串)名称映射到函数对象,类对象或字符串。从中找到的函数和类对象文档字符串M.__test__
被搜索,字符串被视为文档字符串。在输出,一键K
在M.__test__
出现与名称
1 | <name of M>.__test__.K |
找到的任何类都以相似的方式递归搜索,以测试其包含的方法和嵌套类中的文档字符串。
在版本2.4中进行了更改:“专用名称”概念已被弃用且不再有记录。
3.2. Docstring示例如何被认可?
在大多数情况下,交互式控制台会话的复制和粘贴工作正常,但doctest并不试图精确模拟任何特定的Python shell。
1 | # comments are ignored |
任何期望的输出必须紧跟在包含代码的最后一行'>>> '
或'... '
一行之后,并且预期的输出(如果有的话)扩展到下一行'>>> '
或全空白行。
细则:
预期的输出不能包含全空白行,因为这样的行被用来表示预期输出的结束。如果预期的输出包含空白行,请
<BLANKLINE>
在doctest示例中输入空行。新的2.4版本:<BLANKLINE>
加入; 没有办法在以前的版本中使用包含空行的预期输出。所有硬标签字符都被扩展为空格,使用8列制表位。测试代码生成的输出中的选项卡不会被修改。由于示例输出中的任何硬标签都是展开的,这意味着如果代码输出包含硬标签,则doctest可以通过的唯一方式是如果
NORMALIZE_WHITESPACE
选项或指令有效。或者,可以重写测试以捕获输出并将其作为测试的一部分与预期值进行比较。源代码中对制表符的处理是通过反复试验得出的,并且已被证明是处理它们的最不容易出错的方式。通过编写自定义DocTestParser
类,可以使用不同的算法来处理选项卡。输出到标准输出被捕获,但不输出到标准错误(异常追溯通过不同的方式捕获)。
如果在交互式会话中通过反斜线继续行,或者出于任何其他原因使用反斜杠,则应该使用原始文档字符串,该字符串将按照键入时的方式保存反斜杠:
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
- 起始栏无关紧要:
1 | assert "Easy!" |
并且从开始示例的初始行中出现的预期输出中删除了许多主要的空白字符'>>>'
。
什么是执行上下文?
默认情况下,每次doctest
发现一个文档字符串进行测试,它采用的是 浅拷贝的M
的全局,使运行测试不会改变模块真实的全局,因此,在一个测试M
不能离开屑不小心让另外一个背后测试工作。这意味着示例可以自由使用任何在顶层定义的M
名称,以及在运行的文档字符串中定义的名称。示例无法看到其他文档中定义的名称。
你可以通过强制使用自己的字典作为执行上下文 globs=your_dict
来testmod()
或testfile()
替代。
什么是例外?
没问题,只要回溯是该示例生成的唯一输出:只需粘贴回溯。[1]由于回溯包含可能快速变化的细节(例如,确切的文件路径和行号),所以这是doctest很难灵活接受的一种情况。
简单的例子:
1 | 1, 2, 3].remove(42) [ |
该文档测试成功,如果ValueError
提出,详情如图所示。list.remove(x):
x
not
in
list
预期的异常输出必须以追溯标题开头,该标题可以是以下两行中的任一行,缩写与示例的第一行相同:
1 | Traceback (most recent call last): |
traceback头后面跟着一个可选的traceback堆栈,其内容被doctest忽略。回溯堆栈通常被忽略,或者从交互式会话逐字复制。
跟踪堆栈后面是最有趣的部分:包含异常类型和细节的行。这通常是追溯的最后一行,但如果异常具有多行详细信息,则可以跨越多行:
1 | raise ValueError('multi\n line\ndetail') |
最后三行(以开始ValueError
)与异常的类型和细节进行比较,其余部分将被忽略。
最佳做法是省略追溯堆栈,除非它为示例增加了重要的文档值。所以最后一个例子可能更好,因为:
1 | raise ValueError('multi\n line\ndetail') |
请注意,回溯处理非常特别。特别是,在改写的例子中,使用...
独立于doctest的 ELLIPSIS
选项。这个例子中的省略号可以省略,或者可以是三个(或三百个)逗号或数字,或者Monty Python skit的缩进记录。
一些细节你应该阅读一次,但不需要记住:
Doctest无法猜测您的预期输出是来自异常追溯还是来自普通打印。因此,例如,预计
ValueError: 42 is prime
会传递一个示例,无论是否ValueError
实际提出,或者该示例仅打印该追溯文本。实际上,普通输出很少以追溯标题行开始,所以这不会产生实际问题。回溯堆栈的每一行(如果存在)必须比示例的第一行缩进得更远,或者以非字母数字字符开始。追溯标题后面的第一行缩写相同,并以字母数字开头,作为异常详细信息的开始。当然这对于真正的回溯来说是正确的。
当
IGNORE_EXCEPTION_DETAIL
指定doctest选项时,将忽略最左侧冒号后面的所有内容以及异常名称中的所有模块信息。交互式shell省略了一些
SyntaxError
s 的追溯标题行。但doctest使用traceback标题行来区分异常和非异常。因此,在极少数情况下,如果您需要测试一个SyntaxError
省略traceback头的测试,则需要手动将traceback头行添加到测试示例中。对于某些
SyntaxError
s,Python使用^
标记来显示语法错误的字符位置:
1 1 File “
“, line 1 1 1 ^ SyntaxError: invalid syntax
由于显示错误位置的行出现在异常类型和细节之前,因此它们不会被doctest检查。例如,即使将^
标记放在错误的位置,也会通过以下测试:
1 1 File “
“, line 1 1 1 ^ SyntaxError: invalid syntax
3.5. Option Flags
许多选项标志控制着doctest行为的各个方面。这些标志的符号名称作为模块常量提供,可以按位或运算并传递给各种函数。这些名称也可以在doctest指令中使用。
第一组选项定义测试语义,控制doctest如何确定实际输出是否与示例预期输出相匹配的方面:
1 | doctest.DONT_ACCEPT_TRUE_FOR_1 |
默认情况下,如果预期的输出块只包含1
,只是含有实际输出块1
或仅True
被认为是一个匹配,并类似地用于0
对False
。当DONT_ACCEPT_TRUE_FOR_1
指定时,不允许替换。缺省行为迎合了Python将许多函数的返回类型从整数更改为布尔值; 希望“小整数”输出的doctests在这些情况下仍然有效。这个选项可能会消失,但不会持续数年。
1 | doctest.DONT_ACCEPT_BLANKLINE |
默认情况下,如果预期的输出块包含仅包含字符串的行<BLANKLINE>
,则该行将匹配实际输出中的空行。由于真正的空行界定了预期的输出,因此这是沟通预期空行的唯一方式。什么时候DONT_ACCEPT_BLANKLINE
被指定,这个替代是不允许的。
1 | doctest.NORMALIZE_WHITESPACE |
指定时,所有空白(空格和换行符)都被视为相等。预期输出中的任何空白序列都将与实际输出中的任何空白序列相匹配。默认情况下,空白必须完全匹配。NORMALIZE_WHITESPACE
当预期输出的行很长时,并且您想要在源代码中的多行中包装它时,它特别有用。
1 | doctest.ELLIPSIS |
指定时,...
预期输出中的省略号标记()可以匹配实际输出中的任何子字符串。这包括跨越行边界的子字符串和空的子字符串,所以最好保持简单的使用。复杂的用途可能会导致相同类型的“oops,它匹配得太多了!” .*
在正则表达式中很容易出现意外。
1 | doctest.IGNORE_EXCEPTION_DETAIL |
指定时,即使异常详细信息不匹配,如果引发了期望类型的异常,那么期望异常的示例也会通过。例如,ValueError: 42
如果引发的实际异常是预期的例子ValueError: 3*14
,但会失败,例如,如果TypeError
引发。
它也会忽略Python 3 doctest报告中使用的模块名称。因此,无论测试是在Python 2.7还是Python 3.2(或更高版本)下运行,这两种变体都可以与指定的标志一起使用:
1 | >>> raise CustomError('message') |
请注意,ELLIPSIS
也可以用于忽略异常消息的详细信息,但根据是否将模块详细信息作为异常名称的一部分进行打印,此类测试可能仍会失败。使用IGNORE_EXCEPTION_DETAIL
和来自Python 2.3的细节也是编写文档测试的唯一明确方式,它不关心异常细节,但仍然在Python 2.3或更低版本中继续传递(这些版本不支持doctest指令并将它们忽略为不相关的注释) 。例如:
1 | >>> (1, 2)[3] = 'moo' |
虽然Python 2.4中的细节更改为“不”而不是“不”,但在Python 2.3以及更高版本的Python版本中通过了指定的标志。
在 3.2 版更改::IGNORE_EXCEPTION_DETAIL
现在也忽略了与包含被测异常的模块有关的任何信息
1 | doctest.SKIP |
指定时,请不要运行该示例。这在doctest示例既可用作文档也可用作测试用例的情况下非常有用,应将其用于文档目的,但不应进行检查。例如,该示例的输出可能是随机的; 或者该示例可能依赖于测试驱动程序无法使用的资源。
SKIP标志也可用于临时“注释”示例。
1 | doctest.COMPARISON_FLAGS |
将上面的所有比较标志掩盖起来。
第二组选项控制如何报告测试失败:
1 | doctest.REPORT_UDIFF |
指定时,涉及多行预期和实际输出的故障将使用统一差异显示。
1 | doctest.REPORT_CDIFF |
指定时,涉及多行预期输出和实际输出的故障将使用上下文差异显示。
1 | doctest.REPORT_NDIFF |
指定时,difflib.Differ
使用与常用ndiff.py
实用程序相同的算法计算差异。这是标记线内和线间差异的唯一方法。例如,如果预期输出的一行包含数字1
,其中实际输出包含字母l
,则会插入一行,并在其中插入用于标记不匹配列位置的插入符号。
1 | doctest.REPORT_ONLY_FIRST_FAILURE |
指定时,显示每个doctest中的第一个失败示例,但禁止所有其他示例的输出。这将防止doctest报告因早期故障而中断的正确示例; 但它也可能隐藏不正确的例子,不依靠第一次失败而失败。当REPORT_ONLY_FIRST_FAILURE
指定时,剩余的示例仍在运行,并仍然计入报告的故障总数; 只有输出被抑制。
1 | doctest.FAIL_FAST |
指定时,在第一个失败示例后退出,不要尝试运行其余示例。因此,报告的故障数最多为 1。此标志在调试期间可能很有用,因为第一次失败后的示例不会生成调试输出。
3.4版新功能
还有一种方法可以注册新的选项标志名称,但除非您打算doctest
通过子类扩展内部函数,否则这种方法并不有用。
1 | doctest.register_optionflag(name) |
用给定名称创建一个新选项标志,并返回新标志的整数值。register_optionflag()
可用于子类化OutputChecker
或DocTestRunner
创建您的子类支持的新选项。register_optionflag()
应该总是使用以下习惯用法来调用:
1 | MY_FLAG = register_optionflag('MY_FLAG') |
New in version 2.4.
3.6. Directives
Doctest指令可用于修改单个示例的选项标志。Doctest指令是遵循示例源代码的特殊Python注释:
1 | directive ::= "#" "doctest:" directive_options |
+
or -
和指令选项名称之间不允许有空格。指令选项名称可以是上面解释的任何选项标志名称。
一个例子的doctest指令修改了doctest的这个例子的行为。使用+
启用这个名字的行为,或-
将其禁用。
例如,这个测试通过:
1 | print range(20) # doctest: +NORMALIZE_WHITESPACE |
如果没有指令,它会失败,这是因为实际输出在单个数字列表元素之前没有两个空格,并且因为实际输出在单行上。这个测试也通过了,并且还需要一个指令来做到这一点:
1 | print range(20) # doctest: +ELLIPSIS |
多条指令可用于单条物理线路,用逗号分隔:
1 | print range(20) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE |
如果单个示例使用多个指令注释,则将它们合并:
1 | >>> print range(20) # doctest: +ELLIPSIS |
如前例所示,您可以将...
行添加到仅包含指令的示例中。当一个例子对于指令很容易适合同一行时太长了,这会很有用:
1 | >>> print range(5) + range(10,20) + range(30,40) + range(50,60) |
请注意,由于默认情况下所有选项都被禁用,并且指令仅适用于它们出现的示例,因此启用选项(通过+
指令)通常是唯一有意义的选择。但是,选项标志也可以传递给运行doctests的函数,建立不同的默认值。在这种情况下,通过-
指令禁用选项可能很有用。
2.4版新增功能:增加了对doctest指令的支持。
3.7. 警告
doctest
严格要求在预期产出中要求完全匹配。如果即使单个字符不匹配,测试也会失败。这可能会让你感到惊讶,因为你确切地知道Python做了什么,并且不能保证输出。例如,在打印字典时,Python不保证键值对将以任何特定的顺序打印,因此像
1 | foo() |
很脆弱!一种解决方法是做
1 | "Hermione": "hippogryph", "Harry": "broomstick"} foo() == { |
代替。另一个是要做的
1 | >>> d = foo().items() |
还有其他的,但你明白了。
另一个不好的想法是打印嵌入对象地址的东西,比如
1 | 1.0) # certain to fail some of the time id( |
ELLIPSIS
指令为最后一个示例提供了一个很好的方法:
1 | #doctest: +ELLIPSIS C() |
浮点数也受到跨平台的小输出变化的影响,因为Python遵循平台C库进行浮点格式化,而C库在质量上差别很大。
1 | >>> 1./7 # risky |
表格I/2.**J
中的数字在所有平台上都是安全的,而且我通常会编写一些doctest的例子来生成这种格式的数字:
1 | 3./4 # utterly safe |
简单的分数对于人们来说也更容易理解,并且这使得更好的文档。
4.基本API
函数testmod()
和testfile()
提供了一个简单的界面,文档测试,应该是足够了最基本的用途。有关这两个函数的不太正式的介绍,请参见简单用法:检查文档字符串和简单用法中的示例:检查文本文件中的示例。
1 | doctest.testfile(filename[, module_relative][, name][, package][, globs][, verbose][, report][, optionflags][, extraglobs][, raise_on_error][, parser][, encoding]) |
除文件名以外的所有参数都是可选的,并应以关键字形式指定。
在名为filename的文件中测试示例。返回(failure_count, test_count)
。
可选参数module_relative指定应如何解释文件名:
如果module_relative是
True
(缺省值),则filename指定一个与操作系统无关的模块相对路径。默认情况下,这个路径是相对于调用模块的目录;但是如果指定了package参数,那么它与该包相关。为了确保OS独立性,文件名应该使用/
字符来分隔路径段,并且可能不是绝对路径(即它可能不以开头/
)。如果module_relative是
False
,则filename指定一个OS特定的路径。路径可以是绝对的或相对的; 相对路径相对于当前工作目录被解析。
可选参数名称给出测试的名称; 默认情况下,或者None
,os.path.basename(filename)
被使用。
可选参数包是一个Python包或一个Python包的名称,其目录应该用作模块相关文件名的基本目录。如果未指定包,则调用模块的目录将用作模块相关文件名的基本目录。如果module_relative是指定包,那是错误的。False
可选参数globs在执行示例时给出了一个用作全局变量的字典。这个词典的一个新的浅拷贝是为doctest创建的,所以它的例子从一个干净的石板开始。默认情况下,或者如果None
使用新的空字典。
可选参数extraglobs给出了一个合并到用于执行示例的全局变量中的字典。这适用于dict.update()
:如果globs和extraglobs具有共同的键,则extraglobs中的关联值出现在组合字典中。默认情况下,或者如果None
,不使用额外的全局变量。这是一个允许doctests参数化的高级功能。例如,可以为基类编写一个doctest,使用该类的通用名称,然后通过传递将泛型名称映射到要测试的子类的extraglobs字典来测试任意数量的子类。
可选参数详细信息如果为true,则会打印大量内容,如果为false,则仅打印失败; 默认情况下,或者如果None
,当且仅当'-v'
在时才是sys.argv
。
可选的参数报告在最后打印摘要时为真,否则在最后不打印任何内容。在详细模式下,摘要是详细的,否则摘要非常简短(实际上,如果所有测试都通过,则为空)。
可选参数optionflags或一起选项标志。请参见选项标志部分。
可选参数raise_on_error默认为false。如果为true,则在例子中发生第一次失败或意外异常时引发异常。这样可以对故障进行事后调试。默认行为是继续运行示例。
可选参数解析器指定DocTestParser
应该用于从文件中提取测试的一个(或子类)。它默认为一个普通的解析器(即,DocTestParser()
)。
可选的参数编码指定应该用于将文件转换为unicode的编码。
2.4版本中的新功能。
在版本2.5中更改:添加了参数编码。
1 | doctest.testmod([m][, name][, globs][, verbose][, report][, optionflags][, extraglobs][, raise_on_error][, exclude_empty]) |
所有参数都是可选的,除m外的所有参数都应以关键字形式指定。
在从m开始可以访问的函数和类中的docstrings中的测试示例(或者__main__
如果m没有提供或者是None
,则为module ),以m.__doc__
。开头。
还有可以从字典中获得的测试例子m.__test__
,如果它存在与否None
。m.__test__
将名称(字符串)映射到函数,类和字符串; 函数和类docstrings搜索的例子; 字符串被直接搜索,就好像它们是docstrings一样。
仅搜索附加到属于模块m的对象的文档字符串。
返回(failure_count, test_count)
。
可选参数名称给出模块的名称; 默认情况下,或者None
,m.__name__
被使用。
可选参数exclude_empty默认为false。如果属实,则没有找到doctests的对象将被排除在考虑之外。默认值是向后兼容的黑客,使代码仍然使用doctest.master.summarize()
连同testmod()
继续得到输出,没有测试对象。新构造函数的exclude_empty参数DocTestFinder
默认为true。
可选参数extraglobs,verbose,report,optionflags,raise_on_error和globs与testfile()
上述函数相同,只是globs默认为m.__dict__
。
在版本2.3中更改:添加了参数optionflags。
在版本2.4中更改:添加了参数extraglobs,raise_on_error和exclude_empty。
在版本2.5中更改:可选参数isprivate,在2.4中弃用,已被删除。
1 | doctest.run_docstring_examples(f, globs[, verbose][, name][, compileflags][, optionflags]) |
与对象f相关的测试例子; 例如,f可以是字符串,模块,函数或类对象。
字典参数glob的浅拷贝用于执行上下文。
失败消息中使用可选参数名称,缺省值为"NoName"
。
如果可选参数verbose为true,则即使没有失败,也会生成输出。默认情况下,仅在发生示例故障时才会生成输出。
可选参数compileflags给出了运行示例时应由Python编译器使用的一组标志。默认情况下,或者如果None
,推导的标志对应于在globs中找到的一组未来特征。
可选参数optionflags与testfile()
上面的函数一样。
5. Unittest API
随着您的文档测试模块集合的增长,您需要一种系统地运行所有文档测试的方法。在Python 2.4之前,doctest
有一个几乎没有文档记录的Tester
类,它提供了一个基本方法来组合多个模块的doctests。Tester
是虚弱的,实际上最严重的Python测试框架建立在unittest
模块上,它提供了许多灵活的方法来结合多个来源的测试。所以,在Python 2.4,doctest
的Tester
类已被弃用,doctest
提供了可用于创建两个函数unittest
从模块和包含文档测试的文本文件的测试套件。要与unittest
测试发现集成,load_tests()
在您的测试模块中包含一个函数:
1 | import unittest |
有两个主要的功能可以用unittest.TestSuite
文本文件和模块创建实例:
1 | doctest.DocFileSuite(*paths, [module_relative][, package][, setUp][, tearDown][, globs][, optionflags][, parser][, encoding]) |
将doctest测试从一个或多个文本文件转换为unittest.TestSuite
。
返回的unittest.TestSuite
内容将由unittest框架运行并在每个文件中运行交互式示例。如果任何文件中的示例失败,则合成的单元测试将失败,并failureException
引发异常,显示包含测试的文件的名称和一个(有时是近似的)行号。
将一个或多个路径(字符串)传递给要检查的文本文件。
选项可以作为关键字参数提供:
可选参数module_relative指定应如何解释路径中的文件名:
如果module_relative是
True
(缺省值),则路径中的每个文件名指定与操作系统无关的模块相对路径。默认情况下,这个路径是相对于调用模块的目录; 但是如果指定了package参数,那么它与该包相关。为确保操作系统无关性,每个文件名应使用/
字符来分隔路径段,并且可能不是绝对路径(即可能不以其开头/
)。如果module_relative是
False
,则路径中的每个文件名都指定一个OS特定的路径。路径可以是绝对的或相对的; 相对路径相对于当前工作目录被解析。
可选参数包是Python包或Python包的名称,其目录应该用作路径中与模块相关的文件名的基本目录。如果未指定包,则调用模块的目录将用作模块相关文件名的基本目录。如果module_relative是指定包,那是False
错误的。
可选参数setUp指定测试套件的设置函数。这在每个文件中运行测试之前被调用。该的setUp函数将被传递一个DocTest
对象。setUp函数可以在测试的globs属性通过时访问测试全局变量。
可选参数tearDown指定测试套件的拆卸函数。这是在每个文件中运行测试后调用的。在拆卸会被传递给一个DocTest
对象。setUp函数可以在测试的globs属性通过时访问测试全局变量。
可选参数globs是包含测试的初始全局变量的字典。每个测试都会创建一本新字典。默认情况下,globs是一个新的空字典。
可选参数optionflags指定测试的默认doctest选项,由各个选项标记组合或创建。请参见选项标志部分。请参阅set_unittest_reportflags()
下面的功能以更好地设置报告选项。
可选参数解析器指定DocTestParser
应该用于从文件中提取测试的一个(或子类)。它默认为一个普通的解析器(即,DocTestParser()
)。
可选的参数编码指定应该用于将文件转换为unicode的编码。
2.4版本中的新功能。
在版本2.5中进行了更改:将全局__file__
添加到提供给使用文本文件加载的文档测试的全局文件中DocFileSuite()
。
在版本2.5中更改:添加了参数编码。
注意
不像testmod()
和DocTestFinder
,ValueError
如果模块不包含文档字符串,该函数会引发一个问题。您可以通过传递一个DocTestFinder
实例作为test_finder参数,并将其exclude_empty关键字参数设置为False
:
1 | False) finder = doctest.DocTestFinder(exclude_empty= |
将模块的doctest测试转换为一个模块unittest.TestSuite
。
返回的unittest.TestSuite
是由unittest框架运行并在模块中运行每个doctest。如果有任何文档测试失败,则合成的单元测试失败,并failureException
引发异常,显示包含测试的文件的名称和一个(有时是近似的)行号。
可选参数模块提供要测试的模块。它可以是一个模块对象或一个(可能点缀的)模块名称。如果未指定,则使用调用此函数的模块。
可选参数globs是包含测试的初始全局变量的字典。每个测试都会创建一本新字典。默认情况下,globs是一个新的空字典。
可选参数extraglobs指定一组额外的全局变量,这是合并成的水珠。默认情况下,不使用额外的全局变量。
可选参数test_finder是DocTestFinder
用于从模块中提取doctests 的对象(或插入替换)。
可选参数setUp,tearDown和optionflags与DocFileSuite()
上面的函数相同。
2.3版本的新功能。
在版本2.4中更改:添加了参数globs,extraglobs,test_finder,setUp,tearDown和optionflags ; 这个功能现在使用和。一样的搜索技术testmod()
。
在幕后,DocTestSuite()
创建unittest.TestSuite
出的doctest.DocTestCase
实例,DocTestCase
是的子类unittest.TestCase
。DocTestCase
这里没有记录(这是一个内部细节),但是研究它的代码可以回答关于unittest
集成确切细节的问题。
同样,DocFileSuite()
创建unittest.TestSuite
出的doctest.DocFileCase
实例,DocFileCase
是的子类DocTestCase
。
因此创建unittest.TestSuite
运行实例的两种方式DocTestCase
。这对于一个微妙的原因很重要:当你doctest
自己运行函数时,可以doctest
通过将选项标志传递给doctest
函数来直接控制正在使用的选项。但是,如果你正在编写一个unittest
框架,unittest
最终控制何时以及如何运行测试。框架作者通常希望控制doctest
报表选项(可能例如由命令行选项指定),但是没有办法将选项传递unittest
给doctest
测试运行者。
出于这个原因,doctest
还支持通过此功能支持doctest
特定于unittest
支持的报告标志的概念:
1 | doctest.set_unittest_reportflags(flags) |
设置doctest
要使用的报告标志。
参数标志或一起选项标志。请参见选项标志部分。只能使用“报告标志”。
这是一个模块全局设置,并影响模块运行的所有将来的doctests unittest
:在DocTestCase
实例构建时查看为测试用例指定的选项标记DocTestCase
的runTest()
方法。如果没有指定报告的标志(这是典型的和预期的情况下),doctest
的unittest
报告标志是按位或运算进入选项标志,因此增强选项标志传递给创建运行文档测试DocTestRunner
实例。如果时指定的任何报告的标志例如构建DocTestCase
,doctest
的unittest
报告标志被忽略。
在函数被调用之前生效的报告标志的值由函数返回unittest
。
New in version 2.4.
6. Advanced API
基本的API是一个简单的包装,旨在使doctest易于使用。它相当灵活,应该满足大多数用户的需求; 但是,如果您需要对测试进行更精细的控制,或者希望扩展doctest的功能,那么您应该使用高级API。
高级API围绕两个容器类进行,这两个容器类用于存储从doctest案例中提取的交互式示例:
Example
:一个Python 语句,与它的预期输出配对。DocTest
:Example
s 的集合,通常从单个文档字符串或文本文件中提取。
定义其他处理类来查找,分析和运行,并检查doctest示例:
DocTestFinder
:查找给定模块中的所有文档字符串,并使用DocTestParser
aDocTest
从包含交互式示例的每个文档字符串中创建一个。DocTestParser
:DocTest
从字符串中创建一个对象(例如对象的文档字符串)。DocTestRunner
:执行DocTest
中的例子,并使用一个OutputChecker
来验证它们的输出。OutputChecker
:将doctest示例中的实际输出与预期输出进行比较,并确定它们是否匹配。
下图总结了这些处理类之间的关系:
1 | list of: |
6.1. DocTest Objects
1 | class doctest.DocTest(examples, globs, name, filename, lineno, docstring) |
应该在单个命名空间中运行的doctest示例的集合。构造函数参数用于初始化相同名称的属性。
2.4版本中的新功能。
DocTest
定义了以下属性。它们由构造函数初始化,不应该直接修改。
1 | examples |
Example
编码应该由此测试运行的各个交互式Python示例的对象列表。
1 | globs |
应该运行示例的名称空间(又称全局变量)。这是一个将名称映射到值的字典。globs
在测试运行之后,示例所做的任何对名称空间的更改(例如绑定新变量)都会反映出来。
1 | name |
一个字符串名称标识DocTest
。通常,这是测试从中提取的对象或文件的名称。
1 | filename |
这DocTest
是从中提取的文件的名称; 或者None
如果文件名是未知的,或者如果DocTest
没有从文件中提取。
1 | lineno |
行号在filename
哪里DocTest
开始,或None
行号是否不可用。该行号相对于文件的开头是从零开始的。
1 | docstring |
从中提取测试None
的字符串,或者字符串不可用,或者测试未从字符串中提取。
6.2. 示例对象
1 | class doctest.Example(source, want[, exc_msg][, lineno][, indent][, options]) |
一个交互式示例,由Python语句及其预期输出组成。构造函数参数用于初始化相同名称的属性。
2.4版本中的新功能。
Example
定义了以下属性。它们由构造函数初始化,不应该直接修改。
1 | source |
包含示例源代码的字符串。这个源代码由一个Python语句组成,并且总是以换行符结尾; 构造函数在必要时添加一个换行符。
1 | want |
运行示例源代码的预期输出(来自标准输出,或者异常情况下的回溯)。want
除非没有输出,否则以换行符结束,在这种情况下,它是一个空字符串。构造函数在必要时添加一个换行符。
1 | exc_msg |
该示例生成的异常消息,如果该示例预计会生成异常; 或者None
如果不希望产生异常。该异常消息与返回值进行比较traceback.format_exception_only()
。exc_msg
除非是换行符,否则以换行符结尾None
。如果需要,构造函数会添加一个换行符。
1 | lineno |
包含示例开始处的示例的字符串中的行号。该行号相对于包含字符串的开头是从零开始的。
1 | indent |
包含字符串中的示例缩进,即示例第一个提示之前的空格字符数。
1 | options |
从选项标记到True
or的字典映射False
,用于覆盖此示例的默认选项。任何未包含在此字典中的选项标志都保留默认值(由DocTestRunner
s 指定optionflags
)。默认情况下,不设置任何选项。
6.3. DocTestFinder对象
1 | class doctest.DocTestFinder([verbose][, parser][, recurse][, exclude_empty]) |
一个处理类,用于DocTest
从文档字符串及其包含对象的文档字符串中提取与给定对象相关的s。DocTest
s可以从下列对象类型中提取:模块,函数,类,方法,静态方法,类方法和属性。
可选参数verbose可用于显示查找器搜索的对象。它默认为False
(不输出)。
可选参数解析器指定DocTestParser
用于从文档字符串中提取文档测试的对象(或插入替换)。
如果可选参数recurse为false,那么DocTestFinder.find()
将只检查给定的对象,而不检查任何包含的对象。
如果可选参数exclude_empty为false,DocTestFinder.find()
则将包含具有空文档字符串的对象的测试。
2.4版本中的新功能。
DocTestFinder
定义了以下方法:
1 | find(obj[, name][, module][, globs][, extraglobs]) |
返回DocTest
由obj的文档字符串或其包含的任何对象的文档字符串定义的s 的列表。
可选参数名称指定对象的名称; 这个名字将被用来为返回的DocTest
s 构造名字。如果没有指定名称,则obj.__name__
使用。
可选参数模块是包含给定对象的模块。如果模块没有被指定或者是None
,则测试发现者将尝试自动确定正确的模块。使用该对象的模块:
作为默认命名空间,如果没有指定globs。
阻止DocTestFinder从其他模块导入的对象中提取DocTests。(包含模块以外的模块的包含对象将被忽略。)
查找包含该对象的文件的名称。
帮助查找文件中对象的行号。
如果模块是False
,则不会尝试找到该模块。这是很晦涩的,主要用于测试doctest本身:如果module是False
,或者是None
但不能自动找到,那么所有对象都被认为属于(不存在的)模块,因此所有包含的对象将(递归地)被搜索为doctests。
对于每个全局DocTest
通过组合形成水珠和extraglobs(在绑定extraglobs倍率绑定在水珠)。为每个字典创建一个新的globals字典的浅表副本DocTest
。如果未指定globs,则默认为模块的_dict (如果已指定)或*{}*
以其他方式指定。如果_extraglobs没有被指定,那么它默认为{}
。
6.4. DocTestParser对象
1 | class doctest.DocTestParser |
一个处理类,用于从字符串中提取交互式示例,并使用它们创建DocTest
对象。
2.4版本中的新功能。
DocTestParser
定义了以下方法:
1 | get_doctest(string, globs, name, filename, lineno) |
从给定的字符串中提取所有doctest示例,并将它们收集到一个DocTest
对象中。
globs,name,filename和lineno是新DocTest
对象的属性。请参阅文档以DocTest
获取更多信息。
1 | get_examples(string[, name]) |
从给定的字符串中提取所有doctest示例,并将它们作为Example
对象列表返回。行号是从0开始的。可选参数名称是标识此字符串的名称,仅用于错误消息。
1 | parse(string[, name]) |
将给定的字符串分成示例和干预文本,并将它们作为交替Example
s和字符串的列表返回。Example
s的行号是基于0的。可选参数名称是标识此字符串的名称,仅用于错误消息。
6.5. DocTestRunner对象
1 | class doctest.DocTestRunner([checker][, verbose][, optionflags]) |
处理类用于执行和验证DocTest
中的交互式示例。
预期产出与实际产出之间的比较由OutputChecker
。这种比较可以用许多选项标志来定制; 有关更多信息,请参阅选项标志部分。如果选项标志不足,则可以通过OutputChecker
向构造函数传递一个子类来定制比较。
测试运行者的显示输出可以通过两种方式进行控制。首先,可以传递一个输出函数TestRunner.run()
; 这个函数将会被显示的字符串调用。它默认为sys.stdout.write
。如果捕获的输出不充分,则显示输出也可以通过继承DocTestRunner,并覆盖方法定制report_start()
,report_success()
,report_unexpected_exception()
,和report_failure()
。
可选的关键字参数检查器指定OutputChecker
应该用于比较预期输出与doctest示例的实际输出的对象(或插入替换)。
可选的关键字参数verbose控制着DocTestRunner
冗长。如果详细是True
,则会在每个示例运行时打印信息。如果详细是False
,则只打印故障。如果verbose未指定,或者None
使用详细输出,则使用命令行开关-v
。
可选的关键字参数optionflags可用于控制测试运行器如何将预期输出与实际输出进行比较,以及它如何显示故障。有关更多信息,请参见选项标志部分。
2.4版本中的新功能。
DocTestParser
定义了以下方法:
1 | report_start(out, test, example) |
报告测试运行人员即将处理给出的示例。提供此方法以允许其子类DocTestRunner
定制其输出;它不应该直接调用。
例子就是要处理的例子。测试是包含示例的测试。out是传递给的输出函数DocTestRunner.run()
。
1 | report_success(out, test, example, got) |
报告给出的示例已成功运行。提供此方法以允许其子类DocTestRunner
定制其输出;它不应该直接调用。
例子就是要处理的例子。得到的是实例的实际输出。测试是包含示例的测试。out是传递给的输出函数DocTestRunner.run()
。
1 | report_failure(out, test, example, got) |
报告给出的例子失败。提供此方法以允许其子类DocTestRunner
定制其输出; 它不应该直接调用。
例子就是要处理的例子。得到的是实例的实际输出。测试是包含示例的测试。out是传递给的输出函数DocTestRunner.run()
。
1 | report_unexpected_exception(out, test, example, exc_info) |
报告给出的示例引发了意外的异常。提供此方法以允许其子类DocTestRunner
定制其输出; 它不应该直接调用。
例子就是要处理的例子。exc_info是包含有关意外异常(由返回的sys.exc_info()
)的信息的元组。测试是包含示例的测试。out是传递给的输出函数DocTestRunner.run()
。
1 | run(test[, compileflags][, out][, clear_globs]) |
运行在实施例中测试(一个DocTest
对象),并使用写入器功能显示结果出来。
这些示例在命名空间中运行test.globs
。如果clear_globs为true(缺省值),那么该名称空间将在测试运行后清除,以帮助进行垃圾回收。如果您想在测试完成后检查名称空间,请使用clear_globs = False。
compileflags给出了运行示例时Python编译器应该使用的一组标志。如果未指定,则它将默认为适用于globs的future-import标志集。
每个示例的输出都使用DocTestRunner
输出检查器进行检查,并且结果由这些DocTestRunner.report_*()
方法进行格式化。
1 | summarize([verbose]) |
打印由此DocTestRunner运行的所有测试用例的摘要,并返回一个指定的元组 TestResults(failed, attempted)
。
可选的详细参数控制摘要的详细程度。如果没有指定DocTestRunner
详细程度,则使用冗长度。
在版本2.6中更改:使用命名的元组。
6.6. OutputChecker对象
1 | class doctest.OutputChecker |
用于检查doctest示例的实际输出是否与预期输出匹配的类。OutputChecker
定义了两种方法:check_output()
,它比较给定的一对输出,如果匹配则返回真; 并output_difference()
返回一个描述两个输出之间差异的字符串。
2.4版本中的新功能。
OutputChecker
定义了以下方法:
1 | check_output(want, got, optionflags) |
True
如果示例(got)的实际输出与预期输出(想要)匹配,则返回。如果这些字符串相同,则始终认为它们匹配;但取决于测试运行器使用的选项标志,还可以使用几种非精确匹配类型。有关选项标志的更多信息,请参见选项标志部分。
1 | output_difference(example, got, optionflags) |
返回一个字符串,描述给定示例(示例)的预期输出与实际输出(获得)之间的差异。optionflags是用来比较想要和得到的选项标志的集合。
7.调试
Doctest提供了几种调试doctest示例的机制:
几个函数将doctests转换为可执行的Python程序,可以在Python调试器下运行
pdb
。DebugRunner
类是的一个子类DocTestRunner
的是提高用于第一故障的例子的异常,包含有关实施例的信息。该信息可用于对示例执行事后调试。unittest
通过DocTestSuite()
支持由debug()
所定义的方法生成的案例unittest.TestCase
。您可以
pdb.set_trace()
在doctest示例中添加调用,并在执行该行时放入Python调试器。然后你可以检查变量的当前值,等等。例如,假设a.py
只包含这个模块docstring:
“”” >>> 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– >
(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 print x 6 (Pdb) step –Return– > (2)f()->None -> g(x2) (Pdb) list 1 def f(x): 2 -> g(x2) EOF print x 3 (Pdb) step –Return– > (1)?()->None -> f(3) (Pdb) cont (0, 3) >>>
在版本2.4中进行了更改:pdb.set_trace()
添加了在文档测试中使用有用的功能。
将doctests转换为Python代码的函数,并可能在调试器下运行综合代码:
1 | doctest.script_from_examples(s) |
将带有示例的文本转换为脚本。
参数s是一个包含doctest示例的字符串。该字符串被转换为Python脚本,其中s中的doctest示例转换为常规代码,其他所有内容都转换为Python注释。生成的脚本作为字符串返回。例如,
1 | import doctest |
显示:
1 | # Set x and y to 1 and 2. |
该函数在其他函数的内部使用(请参见下文),但是当您想要将交互式Python会话转换为Python脚本时,该函数也很有用。
2.4版本中的新功能。
1 | doctest.testsource(module, name) |
将对象的doctest转换为脚本。
参数模块是一个模块对象,或者一个模块的虚线名称,包含其文档感兴趣的对象。参数名称是具有感兴趣的doctests的对象的名称(在模块内)。结果是一个字符串,包含对象的文档字符串转换为Python脚本,script_from_examples()
如上所述。例如,如果模块a.py
包含顶级函数f()
,那么
1 | import a, doctest |
打印函数f()
的文档字符串的脚本版本,将文档转换为代码,其余部分放在注释中。
2.3版本的新功能。
1 | doctest.debug(module, name[, pm]) |
调试对象的doctests。
该模块和名称参数是相同的功能testsource()
之上。已命名对象的文档字符串的合成Python脚本被写入临时文件,然后该文件在Python调试器的控制下运行pdb
。
module.__dict__
本地和全局执行上下文都使用浅表副本。
可选参数pm控制是否使用验尸调试。如果pm具有真值,则脚本文件将直接运行,并且仅当脚本通过引发未处理的异常终止时才会涉及调试器。如果确实如此,则通过pdb.post_mortem()
从未处理的异常中传递回溯对象来调用验尸调试。如果pm没有被指定,或者是false,那么通过传递一个适当的execfile()
调用来从脚本开始运行脚本pdb.run()
。
2.3版本的新功能。
在版本2.4中更改:添加了pm参数。
1 | doctest.debug_src(src[, pm][, globs]) |
用字符串调试doctests。
这与debug()
上面的函数类似,只是通过src参数直接指定了包含doctest示例的字符串。
可选参数pm与debug()
上面的函数具有相同的含义。
可选的参数globs给出了一个字典,用作本地和全局执行上下文。如果未指定,或者None
使用空字典。如果指定,则使用字典的浅表副本。
2.4版本中的新功能。
DebugRunner
级和特殊的例外可能提高,最感兴趣的测试框架的作者,并且只在这里勾勒。查看源代码,尤其DebugRunner
是docstring(这是一个doctest!)以获取更多详细信息:
1 | class doctest.DebugRunner([checker][, verbose][, optionflags]) |
只要DocTestRunner
遇到故障,它的一个子类就会引发异常。如果发生意外异常,则会引发UnexpectedException
异常,包含测试,示例和原始异常。如果输出不匹配,则会DocTestFailure
引发异常,包含测试,示例和实际输出。
有关构造函数参数和方法的信息,请参阅DocTestRunner
高级API一节中的文档。
DebugRunner
实例可能会引发两个例外情况:
1 | exception doctest.DocTestFailure(test, example, got) |
DocTestRunner
表示doctest示例的实际输出与预期输出不符的异常。构造函数参数用于初始化相同名称的属性。
DocTestFailure
定义了以下属性:
1 | DocTestFailure.test |
DocTest
示例失败时正在运行的对象。
1 | DocTestFailure.example |
Example
失败。
1 | DocTestFailure.got |
示例的实际输出。
1 | exception doctest.UnexpectedException(test, example, exc_info) |
一个异常DocTestRunner
提示表示doctest示例引发了意外异常。构造函数参数用于初始化相同名称的属性。
UnexpectedException
定义了以下属性:
1 | UnexpectedException.test |
DocTest
示例失败时正在运行的对象。
1 | UnexpectedException.example |
Example
失败。
1 | UnexpectedException.exc_info |
包含有关意外异常的信息的元组,返回的是sys.exc_info()
。
8. Soapbox
正如引言中提到的,doctest
已经发展到三个主要用途:
- 检查文档字符串中的示例。
\2. 回归测试。
\3. 可执行文档/文字测试。
这些用途具有不同的要求,区分它们很重要。特别是,用不明确的测试用例填充文档字符串会导致错误的文档。
在编写文档字符串时,请小心选择文档字符串示例。有一个学问需要学习 - 起初可能并不自然。示例应该为文档增加真正的价值。一个很好的例子往往可以说很多话。如果谨慎处理,这些示例对您的用户来说将是非常宝贵的,并且会随着时间的推移和事情的变化而回报多次收集它们所需的时间。我仍然惊讶于我的一个doctest
示例在“无害”更改后停止工作的频率。
Doctest也是回归测试的绝佳工具,特别是如果你不吝啬解释性文本。通过插入散文和例子,跟踪实际正在测试的内容以及为什么更容易。当一个测试失败时,好的散文可以使得更容易找出问题所在,以及应该如何解决问题。的确,您可以在基于代码的测试中编写大量的评论,但很少有程序员会这样做。许多人已经发现使用doctest方法会导致更清晰的测试。也许这只是因为doctest使编写散文比编写代码容易一些,而在代码中编写注释有点困难。我认为它比以上更深刻:编写基于doctest的测试时的自然态度是您想解释软件的优点,并用示例来说明它们。这反过来自然会导致以最简单的功能开始的测试文件,并在逻辑上进展到复杂性和边缘情况。一个连贯的叙述是结果,而不是一组孤立的函数,它们似乎随机地测试孤立的功能位。这是一种不同的态度,产生不同的结果,模糊了测试和解释之间的区别。
回归测试最好局限于专用对象或文件。有几种组织测试的选项:
将包含测试用例的文本文件编写为交互式示例,并使用
testfile()
或测试这些文件DocFileSuite()
。这是推荐的,尽管对于从一开始就使用doctest设计的新项目来说,这是最容易做到的。定义名为
_regrtest_topic
包含单个文档字符串的函数,其中包含指定主题的测试用例。这些功能可以包含在与模块相同的文件中,或者分离到单独的测试文件中。定义
__test__
从回归测试主题到包含测试用例的文档字符串的字典映射。
当您将测试放入模块中时,模块本身可以成为测试运行者。当测试失败时,您可以安排测试运行者在调试问题时仅重新运行失败的doctest。这是一个这样的测试运行者的最小例子:
1 | if __name__ == '__main__': |
脚注
不支持包含预期输出和异常的示例。试图猜测一个结束和另一个开始的地方太容易出错,这也会导致一个令人困惑的测试。