25.2. 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.

    If the result is small enough to fit in an int, return an int.
    Else return a long.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> [factorial(long(n)) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(30)
    265252859812191058636308480000000L
    >>> factorial(30L)
    265252859812191058636308480000000L
    >>> 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)
    265252859812191058636308480000000L

    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打印有关其try操作的详细日志,并在末尾显示摘要:

$ 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(long(n)) for n in range(6)]
Expecting:
    [1, 1, 2, 6, 24, 120]
ok

依此类推,finally以:

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中可以找到特别有用的示例。

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

开始使用 doctest 的最简单方法(但不一定是 continue 进行的方法)是使用以下命令结束每个模块M

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

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

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

python M.py

除非示例失败,否则不会显示任何内容,在这种情况下,失败的示例和失败的原因将打印到 stdout,输出的最后一行是***Test Failed*** N failures.,其中* N *是失败的示例数。

而是使用-v开关运行它:

python M.py -v

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

您可以pass将verbose=True传递给testmod()来强制采用冗长模式,也可以pass将verbose=False禁止它来使它成为详细模式。在这两种情况下,_5 不会被testmod()检查(因此,pass-v或不pass都没有影响)。

从 Python 2.6 开始,还有一个用于运行testmod()的命令行快捷方式。您可以指示 Python 解释器直接从标准库中运行 doctest 模块,并在命令行上传递模块名称:

python -m doctest -v example.py

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

有关testmod()的更多信息,请参见Basic API部分。

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

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()相同的格式将失败的示例和失败的原因打印到 stdout。

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

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

从 Python 2.6 开始,还有一个用于运行testfile()的命令行快捷方式。您可以指示 Python 解释器直接从标准库运行 doctest 模块,并在命令行中传递文件名:

python -m doctest -v example.txt

因为文件名不是以.py结尾,所以doctest推断它必须以testfile()而不是testmod()运行。

有关testfile()的更多信息,请参见Basic API部分。

25.2.3. 这个怎么运作

本节详细检查 doctest 的工作方式:查看 docdoc 的字符串,查找交互式示例的方式,使用的执行上下文,如何处理异常以及如何使用选项标志来控制其行为。这是编写 doctest 示例所需的信息。有关在这些示例上实际运行 doctest 的信息,请参阅以下部分。

25.2.3.1. 检查哪些文档字符串?

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

另外,如果M.__test__存在且为“ true”,则它必须是字典,并且每个条目都将(字符串)名称 Map 到函数对象,类对象或字符串。搜索从M.__test__找到的函数和类对象文档字符串,并将字符串视为文档字符串。在输出中,出现M.__test__中的键K并显示名称

<name of M>.__test__.K

以类似的方式递归搜索所有找到的类,以测试其包含的方法和嵌套类中的文档字符串。

在版本 2.4 中进行了更改:不赞成使用“私人名称”概念,并且不再对其进行记录。

25.2.3.2. Docstring 示例如何识别?

在大多数情况下,交互式控制台会话的复制和粘贴效果很好,但是 doctest 并没有try对任何特定的 Python shell 进行精确的仿真。

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

任何预期的输出必须立即跟随包含代码的最后'>>> ''... '行,并且预期的输出(如果有)扩展到下一个'>>> '或全空白行。

精美印刷品:

2.4 版的新Function:添加了<BLANKLINE>;在以前的版本中,无法使用包含空行的预期输出。

在版本 2.4 中进行了更改:将选项卡扩展到空格是新Function;以前的版本试图保留硬标签,结果令人困惑。

>>> 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.0

并且从预期的输出中剥离了许多开头的空白字符,就像在开始该示例的初始'>>> '行中显示的那样。

25.2.3.3. 什么是执行上下文?

默认情况下,每次doctest找到要测试的文档字符串时,它都会使用M全局变量的“浅表” *,以便运行测试不会更改模块的实际全局变量,并且M中的一个测试也不会留下意外允许其他测试起作用的碎屑。这意味着示例可以自由使用在M中顶级定义的任何名称,以及在运行的文档字符串中较早定义的名称。示例无法看到其他文档字符串中定义的名称。

您可以pass将globs=your_dict传递给testmod()testfile()来强制使用自己的 dict 作为执行上下文。

25.2.3.4. 那么异常呢?

没问题,只要该示例产生的唯一输出是回溯即可:只需粘贴回溯即可。 [1]由于回溯包含可能会快速更改的详细信息(例如,确切的文件路径和行号),因此在这种情况下,doctest 很难灵活地接受。

Simple example:

>>> [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详细信息。

异常的预期输出必须以 tracebackHeaders 开头,该 Headers 可以是以下两行之一,其缩进与示例的第一行相同:

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

跟踪 Headers 后面是可选的跟踪堆栈,其内容被 doctest 忽略。通常Ellipsis回溯堆栈,或从交互式会话中逐字复制。

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

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

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

在版本 2.4 中进行了更改:以前的版本无法处理多行异常的详细信息。

最佳实践是Ellipsis回溯堆栈,除非它为示例增加了重要的文档价值。因此,最后一个示例可能更好:

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

请注意,对回溯的处理非常特殊。特别是,在重写的示例中,...的使用独立于 doctest 的ELLIPSIS选项。在该示例中,Ellipsis号可以Ellipsis,也可以是三个(或三百个)逗号或数字,或者是 Monty Python 标记的缩进抄本。

您应该阅读一次但不需要记住的一些细节:

>>> 1 1
  File "<stdin>", line 1
    1 1
      ^
SyntaxError: invalid syntax

由于显示错误位置的行位于异常类型和详细信息之前,因此 doctest 不会对其进行检查。例如,即使将^标记放在错误的位置,以下测试也会pass:

>>> 1 1
  File "<stdin>", line 1
    1 1
    ^
SyntaxError: invalid syntax

25.2.3.5. 选项标志

许多选项标志控制 doctest 行为的各个方面。标志的符号名称作为模块常量提供,可以一起为bitwise ORed并传递给各种Function。这些名称也可以在doctest directives中使用。

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

它还将忽略 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 或更早版本下 continue pass的 doctest 的唯一清晰方法(这些版本不支持doctest directives并忽略它们作为无关的 Comments) 。例如:

>>> (1, 2)[3] = 'moo'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object doesn't support item assignment

即使指定的标志在 Python 2.4 中更改为“不”而不是“不”,也可以pass带有指定标志的 Python 2.3 和更高版本的 Python 传递。

在 2.7 版中进行了更改:IGNORE_EXCEPTION_DETAIL现在也忽略了与包含被测异常的模块有关的任何信息

SKIP 标志还可以用于临时“Comments”示例。

2.5 版的新Function。

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

版本 2.4 中的新Function:添加了常量DONT_ACCEPT_BLANKLINENORMALIZE_WHITESPACEELLIPSISIGNORE_EXCEPTION_DETAILREPORT_UDIFFREPORT_CDIFFREPORT_NDIFFREPORT_ONLY_FIRST_FAILURECOMPARISON_FLAGSREPORTING_FLAGS

还有一种注册新选项标志名称的方法,尽管除非您打算pass子类扩展doctest内部结构,否则这没有用:

MY_FLAG = register_optionflag('MY_FLAG')

2.4 版的新Function。

25.2.3.6. Directives

Doctest 指令可用于修改单个示例的option flags。 Doctest 指令是在示例源代码之后的特殊 PythonComments:

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 的行为。使用+启用命名的行为,或使用-禁用它。

例如,此测试pass:

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

如果没有该指令,它将会失败,这是因为实际输出在单位数列表元素之前没有两个空格,并且因为实际输出在一行上。此测试也可以pass,并且还需要一条指令来这样做:

>>> print range(20) 
[0, 1, ..., 18, 19]

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

>>> print range(20) 
[0,    1, ...,   18,    19]

如果一个示例使用了多个指令 Comments,则将它们组合在一起:

>>> print range(20) 
...                 
[0,    1, ...,   18,    19]

如前面的示例所示,您可以在仅包含指令的示例中添加...行。当示例太长而指令无法舒适地放在同一行上时,这将很有用:

>>> print range(5) + range(10,20) + range(30,40) + range(50,60)
... 
[0, ..., 4, 10, ..., 19, 30, ..., 39, 50, ..., 59]

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

2.4 版的新Function:添加了对 doctest 指令的支持。

25.2.3.7. Warnings

doctest认真要求在期望的输出中完全匹配。如果一个字符都不匹配,则测试将失败。这可能会使您感到有些惊讶,因为您确切地了解 Python 可以做什么并且不能保证输出。例如,在打印字典时,Python 不保证键值对将以任何特定 Sequences 打印,因此测试类似于

>>> foo()
{"Hermione": "hippogryph", "Harry": "broomstick"}

很脆弱!一种解决方法是

>>> foo() == {"Hermione": "hippogryph", "Harry": "broomstick"}
True

代替。另一个是要做

>>> d = foo().items()
>>> d.sort()
>>> d
[('Harry', 'broomstick'), ('Hermione', 'hippogryph')]

还有其他人,但是您明白了。

另一个坏主意是打印嵌入对象地址的内容,例如

>>> 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

简单的分数也使人们更容易理解,从而可以更好地记录文档。

25.2.4. 基本 API

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

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

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

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

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

可选参数* globs *给出了在执行示例时用作全局变量的字典。将为 doctest 创建此 dict 的新浅表副本,因此其示例以整洁的形式开始。默认情况下,如果为None,则使用新的空字典。

可选参数* extraglobs 给出了一个 dict,它被合并到用于执行示例的全局变量中。这类似于dict.update():如果 globs extraglobs 具有公共密钥,则 extraglobs 中的关联值将出现在组合字典中。默认情况下,如果为None,则不使用额外的全局变量。这是一项高级Function,允许对 doctests 进行参数化。例如,可以使用 Base Class 的通用名称为 Base Class 编写 doctest,然后pass传递将通用名称 Map 到要测试的子类的 extraglobs * dict 来重用以测试任意数量的子类。

可选参数* verbose *如果为 true,则打印很多内容;如果为 false,则仅打印失败;否则为 false。默认情况下,如果None,则仅当'-v'sys.argv中时为 true。

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

可选参数* optionflags *或加在一起的选项标志。参见Option Flags部分。

可选参数* raise_on_error *默认为 false。如果为 true,则在示例中出现第一次失败时会引发异常或意外的异常。这样可以对故障进行事后调试。默认行为是 continue 运行示例。

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

可选参数* encoding *指定用于将文件转换为 unicode 的编码。

2.4 版的新Function。

在版本 2.5 中更改:添加了参数* encoding *。

可从模块* m (或未提供 m *或_的模块main)中以m.__doc__开头的函数和类中的文档字符串中的测试示例。

如果 dict m.__test__存在但不是None,还要测试示例。 m.__test__将名称(字符串)Map 到函数,类和字符串;搜索函数和类文档字符串以获取示例;直接搜索字符串,就好像它们是文档字符串一样。

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

返回(failure_count, test_count)

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

可选参数* exclude_empty 默认为 false。如果为 true,则不考虑未找到文档测试的对象。默认设置是向后兼容 hack,因此仍将doctest.master.summarize()testmod()结合使用的代码将 continue 获取没有测试的对象的输出。较新的DocTestFinder构造函数的 exclude_empty *参数默认为 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 中进行了更改:在 2.4 中弃用了可选参数* isprivate *。

字典参数* globs *的浅表副本用于执行上下文。

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

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

可选参数* compileflags 给出了运行示例时 Python 编译器应使用的标志集。默认情况下,如果为None,则推导对应于 globs *中发现的一组将来Function的标志。

可选参数* optionflags *与上述函数testfile()相同。

25.2.5. 单元测试 API

随着文档测试模块的集合的增长,您将需要一种系统地运行其所有文档测试的方法。在 Python 2.4 之前,doctest几乎没有文档说明Tester类,它提供了一种基本方法来组合来自多个模块的 doctest。 Tester是微不足道的,实际上,大多数严肃的 Python 测试框架都基于unittest模块构建,该模块提供了许多灵活的方法来组合来自多个源的测试。因此,在 Python 2.4 中,不赞成使用doctestTester类,并且doctest提供了两个函数,可用于从包含 doctests 的模块和文本文件中创建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实例有两个主要Function:

返回的unittest.TestSuite将由 unittest 框架运行,并在每个文件中运行交互式示例。如果任何文件中的示例失败,则综合单元测试将失败,并且会引发failureException异常,该异常显示包含测试的文件名和(有时是近似的)行号。

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

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

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

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

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

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

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

可选参数* optionflags *指定测试的默认 doctest 选项,这些选项是pass将各个选项标志或在一起创建的。参见Option Flags部分。有关设置报告选项的更好方法,请参见下面的Functionset_unittest_reportflags()

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

可选参数* encoding *指定用于将文件转换为 unicode 的编码。

2.4 版的新Function。

在版本 2.5 中进行了更改:全局__file__已添加到提供给使用DocFileSuite()从文本文件加载的 doctest 的全局变量中。

在版本 2.5 中更改:添加了参数* encoding *。

Note

testmod()DocTestFinder不同,如果* module 不包含文档字符串,则此函数将引发ValueError。您可以pass传递DocTestFinder实例作为 test_finder 参数并将其 exclude_empty *关键字参数设置为False来防止此错误:

>>> finder = doctest.DocTestFinder(exclude_empty=False)
>>> suite = doctest.DocTestSuite(test_finder=finder)

返回的unittest.TestSuite将由 unittest 框架运行,并运行模块中的每个 doctest。如果任何 doctest 测试失败,则综合单元测试失败,并引发failureException异常,显示包含测试的文件的名称和(有时是近似的)行号。

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

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

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

可选参数* test_finder *是DocTestFinder对象(或嵌入式替换),用于从模块中提取 doctest。

可选参数* setUp tearDown optionflags *与上面的函数DocFileSuite()相同。

2.3 版的新Function。

在版本 2.4 中更改:添加了参数* globs extraglobs test_finder setUp tearDown optionflags *;此Function现在使用与testmod()相同的搜索技术。

在幕后,DocTestSuite()doctest.DocTestCase个实例中创建了unittest.TestSuite,而DocTestCaseunittest.TestCase的子类。此处未记录DocTestCase(这是内部详细信息),但是研究其代码可以回答有关unittest集成的确切详细信息的问题。

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

因此,创建unittest.TestSuite的两种方法都将运行DocTestCase的实例。这很重要,因为有一个微妙的原因:当您自己运行doctest函数时,可以pass将选项标志传递给doctest函数来直接控制doctest选项的使用。但是,如果您正在编写unittest框架,则unittestfinally将控制何时以及如何运行测试。框架作者通常希望控制doctest报告选项(例如,由命令行选项指定),但是无法将选项passunittest传递给doctest测试运行程序。

因此,doctest还pass以下Function支持特定于unittest支持的doctest报告标记的概念:

参数* flags *或在一起的选项标志。参见Option Flags部分。只能使用“报告标志”。

这是模块全局设置,并且会影响模块unittest运行的所有将来的 doctest:DocTestCaserunTest()方法在构造DocTestCase实例时查看为测试用例指定的选项标志。如果未指定报告标志(这是典型的情况和预期的情况),则doctestunittest报告标志将bitwise ORed放入选项标志中,并将如此扩充的选项标志传递到为运行 doctest 而创建的DocTestRunner实例中。如果在构建DocTestCase实例时指定了任何报告标志,则doctestunittest报告标志将被忽略。

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

2.4 版的新Function。

25.2.6. 进阶 API

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

高级 API 围绕两个容器类展开,这些容器类用于存储从 doctest 案例中提取的交互式示例:

定义了其他处理类以查找,解析和运行以及检查 doctest 示例:

下图总结了这些处理类之间的关系:

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

25.2.6.1. DocTest 对象

2.4 版的新Function。

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

25.2.6.2. 示例对象

2.4 版的新Function。

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

25.2.6.3. DocTestFinder 对象

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

可选参数* parser *指定DocTestParser对象(或嵌入式替换),该对象用于从文档字符串中提取文档测试。

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

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

2.4 版的新Function。

DocTestFinder定义以下方法:

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

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

如果* module False,则不会try查找该模块。这是晦涩的,主要用于测试 doctest 本身:如果 module *是FalseNone但无法自动找到,则所有对象都被视为属于(不存在)模块,因此所有包含的对象将(递归)搜索 doctest。

每个DocTest的全局变量是pass组合* globs extraglobs ( extraglobs 中的绑定覆盖 globs 中的绑定)而形成的。为每个DocTest创建一个新的 globals 字典的浅表副本。如果未指定 globs ,则默认为模块的 __ dict __ (如果指定),否则为{}。如果未指定 extraglobs *,则默认为{}

25.2.6.4. DocTestParser 对象

2.4 版的新Function。

DocTestParser定义以下方法:

25.2.6.5. DocTestRunner 对象

预期输出与实际输出之间的比较由OutputChecker完成。可以使用许多选项标记来自定义此比较。有关更多信息,请参见Option Flags部分。如果选项标志不足,则还可以pass将OutputChecker的子类传递给构造函数来自定义比较。

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

可选关键字参数* checker *指定OutputChecker对象(或直接替换),该对象应用于将预期输出与 doctest 示例的实际输出进行比较。

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

可选关键字参数* optionflags *可用于控制测试运行程序如何将预期输出与实际输出进行比较,以及显示故障的方式。有关更多信息,请参见Option Flags部分。

2.4 版的新Function。

DocTestParser定义以下方法:

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

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

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

在 2.6 版中进行了更改:使用命名的 Tuples。

25.2.6.6. OutputChecker 对象

2.4 版的新Function。

OutputChecker定义以下方法:

25.2.7. Debugging

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

"""
>>> 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) print 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) print x
3
(Pdb) step
--Return--
> <doctest a[2]>(1)?()->None
-> f(3)
(Pdb) cont
(0, 3)
>>>

在版本 2.4 中进行了更改:增加了在 doctests 内部有效使用pdb.set_trace()的Function。

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

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

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
""")

displays:

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

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

2.4 版的新Function。

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

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

打印函数f()的 docstring 的脚本版本,并将 doctests 转换为代码,其余部分放在 Comments 中。

2.3 版的新Function。

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

可选参数* pm 控制是否使用事后调试。如果 pm 为真值,则脚本文件将直接运行,并且仅当脚本pass引发未处理的异常终止时,调试器才会参与。如果是这样,那么将passpdb.post_mortem()调用事后调试,从未处理的异常中传递回溯对象。如果未指定 pm *或为 false,则pass将适当的execfile()调用传递给pdb.run()从一开始就在调试器下运行脚本。

2.3 版的新Function。

在版本 2.4 中更改:添加了* pm *参数。

类似于上面的函数debug(),不同之处在于,pass* src *参数直接指定了包含 doctest 示例的字符串。

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

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

2.4 版的新Function。

DebugRunner类及其可能引发的特殊异常是测试框架作者最感兴趣的,并且仅在此处进行草绘。有关更多详细信息,请参见源代码,尤其是DebugRunner的 docstring(这是一个 doctest!)。

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

DebugRunner实例可能会引发两个异常:

DocTestFailure定义以下属性:

UnexpectedException定义以下属性:

25.2.8. Soapbox

如简介中所述,doctest已 Developing 为具有三种主要用途:

这些用途有不同的要求,区分它们很重要。特别是,用晦涩的测试用例填充您的文档字符串会造成不良的文档编制。

编写文档字符串时,请小心选择文档字符串示例。这是一门需要学习的艺术,一开始可能并不自然。示例应为文档增添 true 的价值。一个好榜样通常值得多说。如果精心制作,这些示例对于您的用户将是无价的,并且将随着时间的流逝和事情的变化而返还多次收集它们所花费的时间。我对“无害”更改后doctest个示例停止工作的频率仍然感到惊讶。

Doctest 还是用于回归测试的出色工具,尤其是如果您不漏读解释性 Literals。pass交织散文和示例,可以更轻松地跟踪实际测试的内容以及原因。当测试失败时,好的散文可以使找出问题的根源以及应该如何解决变得容易得多。的确,您可以在基于代码的测试中编写大量 Comments,但很少有程序员这样做。许多人发现使用 doctest 方法可以带来更清晰的测试。也许这仅仅是因为 doctest 使编写散文比编写代码要容易一些,而在代码中编写 Comments 要困难一些。我认为这不仅限于此:在编写基于 doctest 的测试时,自然的态度是您想解释软件的优点,并pass示例进行说明。这自然会导致测试文件以最简单的Function开始,并从逻辑上 Developing 为复杂情况和极端情况。结果是连贯的叙述,而不是孤立的Function的集合,这些孤立的Function似乎随机地测试了孤立的Function。这是一种不同的态度,并产生不同的结果,从而模糊了测试和解释之间的区别。

回归测试最好仅限于专用对象或文件。有几种组织测试的选项:

将测试放在模块中后,该模块本身就可以成为测试运行程序。如果测试失败,则可以安排测试运行器在调试问题时仅重新运行失败的 doctest。这是此类测试运行程序的最小示例:

if __name__ == '__main__':
    import doctest
    flags = doctest.REPORT_NDIFF|doctest.REPORT_ONLY_FIRST_FAILURE
    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))

Footnotes

首页