# `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()
```

```\$ python example.py
\$
```

```\$ 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.
\$
```

## 简单用法：检查Docstrings中的示例¶

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

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

```python M.py
```

`-v` 来运行它来切换，而不是:

```python M.py -v
```

```python -m doctest -v example.py
```

## 简单的用法：检查文本文件中的例子¶

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

```import doctest
doctest.testfile("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
```

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

`testmod()` 一样， `testfile()` 不会显示任何东西，除非一个例子失败。 如果一个例子失败了，那么失败的例子和失败的原因将被打印到stdout，使用的格式与 `testmod()` 相同。

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

```python -m doctest -v example.txt
```

## 它是如何工作的¶

### 哪些文件串被检查了？¶

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

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

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

### 文档串的例子是如何被识别的？¶

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

fine输出：

• 预期输出不能包含一个全白的行，因为这样的行被认为是预期输出的结束信号。 如果预期的输出包含一个空行，在你的测试例子中，在每一个预期有空行的地方加上 `<BLANKLINE>`

• 所有硬制表符都被扩展为空格，使用 8 列的制表符。由测试代码生成的输出中的制表符不会被修改。 因为样本输出中的任何硬制表符都会被扩展，这意味着如果代码输出包括硬制表符，文档测试通过的唯一方法是 `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
```

并从预期的输出中剥离出与开始该例子的初始 `'>>> '` 行中出现的同样多的前导空白字符。

### 异常如何处理？¶

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

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

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

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

• Doctest 不能猜测你的预期输出是来自异常回溯还是来自普通打印。 因此，例如，一个期望 `ValueError: 42 is prime` 的用例将通过测试，无论 `ValueError` 是真的被触发，或者该用例只是打印了该回溯文本。 在实践中，普通输出很少以回溯标题行开始，所以这不会产生真正的问题。

• 回溯堆栈的每一行（如果有的话）必须比例子的第一行缩进， 或者 以一个非字母数字的字符开始。回溯头之后的第一行缩进程度相同，并且以字母数字开始，被认为是异常细节的开始。当然，这对真正的回溯来说是正确的事情。

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

• 交互式 shell 省略了一些 `SyntaxError` 的回溯头行。但 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.DONT_ACCEPT_TRUE_FOR_1

doctest.DONT_ACCEPT_BLANKLINE

doctest.NORMALIZE_WHITESPACE

doctest.ELLIPSIS

doctest.IGNORE_EXCEPTION_DETAIL

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

doctest.SKIP

SKIP标志也可用于临时 "注释" 用例。

doctest.COMPARISON_FLAGS

doctest.REPORT_UDIFF

doctest.REPORT_CDIFF

doctest.REPORT_NDIFF

doctest.REPORT_ONLY_FIRST_FAILURE

doctest.FAIL_FAST

doctest 命令行接受选项 `-f` 作为 `-o FAIL_FAST` 的简洁形式。

doctest.REPORTING_FLAGS

doctest.register_optionflag(name)

```MY_FLAG = register_optionflag('MY_FLAG')
```

### 指令¶

Doctest指令可以用来修改单个例子的 option flags 。 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" | ...
```

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

```>>> 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` 是严格地要求在预期输出中完全匹配。 如果哪怕只有一个字符不匹配，测试就会失败。 这可能会让你吃惊几次，在你确切地了解到 Python 对输出的保证和不保证之前。 例如，当打印一个集合时，Python 不保证元素以任何特定的顺序被打印出来，所以像:

```>>> foo()
{"Hermione", "Harry"}
```

```>>> foo() == {"Hermione", "Harry"}
True
```

```>>> d = sorted(foo())
>>> d
['Harry', 'Hermione']
```

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

```>>> 1./7  # risky
0.14285714285714285
>>> print(1./7) # safer
0.142857142857
>>> print(round(1./7, 6)) # much safer
0.142857
```

```>>> 3./4  # utterly safe
0.75
```

## 基本API¶

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)

• 如果 module_relative`True` (默认)，那么 filename 指定一个独立于操作系统的模块相对路径。 默认情况下，这个路径是相对于调用模块的目录的；但是如果指定了 package 参数，那么它就是相对于该包的。 为了保证操作系统的独立性， filename 应该使用字符来分隔路径段，并且不能是一个绝对路径 (即不能以 `/` 开始)。

• 如果 module_relative`False`，那么 filename 指定了一个操作系统特定的路径。路径可以是绝对的，也可以是相对的；相对路径是相对于当前工作目录而言的。

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

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

dict 参数 globs 的浅层拷贝被用于执行环境。

## Unittest API¶

```import unittest
import doctest
import my_module_with_doctests

return tests
```

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

• 如果 module_relative`True` （默认值），那么 paths 中的每个文件名都指定了一个独立于操作系统的模块相对路径。 默认情况下，这个路径是相对于调用模块的目录的；但是如果指定了 package 参数，那么它就是相对于该包的。 为了保证操作系统的独立性，每个文件名都应该使用字符来分隔路径段，并且不能是绝对路径（即不能以 `/` 开始）。

• 如果 module_relative`False` ，那么 paths 中的每个文件名都指定了一个操作系统特定的路径。 路径可以是绝对的，也可以是相对的；相对路径是关于当前工作目录的解析。

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

exception doctest.failureException

doctest.set_unittest_reportflags(flags)

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

## 高级 API¶

```                            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` 定义了以下属性。 它们由构造函数初始化，不应该被直接修改。

examples

globs

name

filename

lineno

`filename` 中的行号，这个 `DocTest` 开始的地方，或者行号不可用时为 `None`。 这个行号相对于文件的开头来说是零的。

docstring

### Example 物件¶

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

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

source

want

exc_msg

lineno

indent

options

### DocTestFinder 物件¶

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

`DocTestFinder` 定义了以下方法：

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

• 作为一个默认的命名空间，如果没有指定 globs

• 为了防止 DocTestFinder 从其他模块导入的对象中提取 DocTest 。 (包含有除 module 以外的模块的对象会被忽略)。

• 找到包含该对象的文件名。

• 找到该对象在其文件中的行号。

### DocTestParser 物件¶

class doctest.DocTestParser

`DocTestParser` 定义了以下方法：

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

globsnamefilenamelineno 是新的 `DocTest` 对象的属性。 更多信息请参见 `DocTest` 的文档。

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

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

### DocTestRunner 物件¶

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

`DocTestRunner` 定义了以下方法：

report_start(out, test, example)

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

report_success(out, test, example, got)

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

report_failure(out, test, example, got)

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

report_unexpected_exception(out, test, example, exc_info)

example 是将要被处理的用例。 exc_info 是一个元组，包含关于异常的信息（如由 `sys.exc_info()` 返回）。 test 是包含 example 的测试。 out 是传递给 `DocTestRunner.run()` 的输出函数。

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

test （一个 `DocTest` 对象）中运行这些用例，并使用写入函数 out 显示结果。

compileflags 给出了 Python 编译器在运行例子时应该使用的标志集。如果没有指定，那么它将默认为适用于 globs 的 future-import 标志集。

summarize(verbose=None)

### OutputChecker 物件¶

class doctest.OutputChecker

`OutputChecker` 定义了以下方法：

check_output(want, got, optionflags)

output_difference(example, got, optionflags)

## 调试¶

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

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

• `DebugRunner` 类是 `DocTestRunner` 的一个子类，它为第一个失败的用例触发一个异常，包含关于这个用例的信息。这些信息可以用来对这个用例进行事后调试。

• 你可以在 doctest 的用例中加入对 `pdb.set_trace()` 的调用，当这一行被执行时，你会进入 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--
> <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.script_from_examples(s)

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

doctest.testsource(module, name)

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

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

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

`module.__dict__` 的一个浅层拷贝被用于本地和全局的执行环境。

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

`DebugRunner` 类，以及它可能触发的特殊异常，是测试框架作者最感兴趣的，在此仅作简要介绍。请看源代码，特别是 `DebugRunner` 的文档串（这是一个测试！）以了解更多细节。

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

`DocTestRunner` 的一个子类，一旦遇到失败，就会触发一个异常。 如果一个意外的异常发生，就会引发一个 `UnexpectedException` 异常，包含测试、用例和原始异常。 如果输出不匹配，那么就会引发一个 `DocTestFailure` 异常，包含测试、用例和实际输出。

`DebugRunner` 实例可能会触发两种异常。

exception doctest.DocTestFailure(test, example, got)

`DocTestRunner` 触发的异常，表示一个 doctest 用例的实际输出与预期输出不一致。构造函数参数被用来初始化相同名称的属性。

`DocTestFailure` 定义了以下属性:

DocTestFailure.test

DocTestFailure.example
DocTestFailure.got

exception doctest.UnexpectedException(test, example, exc_info)

`UnexpectedException` 定义了以下属性:

UnexpectedException.test

UnexpectedException.example
UnexpectedException.exc_info

## 肥皂盒¶

1. 检查 docstring 中的用例。

2. 回归测试。

3. 可执行的文档/文字测试。

Doctest 也是回归测试的一个很好的工具，特别是如果你不吝啬解释的文字。 通过交错的文本和用例，可以更容易地跟踪真正的测试，以及为什么。当测试失败时，好的文本可以使你更容易弄清问题所在，以及如何解决。 的确，你可以在基于代码的测试中写大量的注释，但很少有程序员这样做。许多人发现，使用 doctest 方法反而能使测试更加清晰。 也许这只是因为 doctest 使写散文比写代码容易一些，而在代码中写注释则有点困难。 我认为它比这更深入：当写一个基于 doctest 的测试时，自然的态度是你想解释你的软件的细微之处，并用例子来说明它们。这反过来又自然地导致了测试文件从最简单的功能开始，然后逻辑地发展到复杂和边缘案例。 一个连贯的叙述是结果，而不是一个孤立的函数集合，似乎是随机的测试孤立的功能位。 这是一种不同的态度，产生不同的结果，模糊了测试和解释之间的区别。

• 编写包含测试案例的文本文件作为交互式例子，并使用 `testfile()``DocFileSuite()` 来测试这些文件。 建议这样做，尽管对于新的项目来说是最容易做到的，从一开始就设计成使用 doctest 。

• 定义命名为 `_regrtest_topic` 的函数，由单个文档串组成，包含命名主题的测试用例。 这些函数可以包含在与模块相同的文件中，或分离出来成为一个单独的测试文件。

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

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