介绍:今天无意中看到了《流畅的Python》本书,据说是有点难的书。
拿到之后就从后往前翻,还没翻到正文,仅仅翻到注意事项,就看到很多陌生的名词,有点伤自尊,真是有点吾生也有涯,而知也无涯的赶脚。但是也只能怀揣着幸好遇见您的心态,否则一辈子都不知自己的无知。闲话少将,今天遇到第一个陌生词就是doctest。这个doctest是本书中程序做测试用的,而且本模块还是一个标准库。学习时也让我感到写测试用例是一名优秀程序员所具备的素质之一。
Doctest是python的一个标准库,主要用来做测试的。本文的学习资源主要依据官网文档:.
下面开始我们的学习。
doctest从名字中可以瞧出来它干什么,doc(文档)+ test(测试)。没错,这个库就是基于程序的的文档(docstring)做的。说到这里也许会恍然大悟,为啥之前读标准库或别人源码时,为啥把测试用例放到docstring中了,以为就是注释而已,其实还有测试的作用,可见doctest也是用心良苦呢。
1.使用样例
doctest主要支持两种测试方式,一种是基于代码的Docstrings,一种是基于测试文件,即将测试用例的代码放到一个文件中,这种方式是为了一些太复杂的测试写入代码,导致代码污染。上面两种方式划分仅仅是根据这个测试用例是放在哪。
(1) 基于Docstrings的测试
我们知道python函数或类是有docsting这个对象的,主要用来存放说明文档。这里的测试就是基于说明文档做的。直接上官方例子,文件名为exam:
"""
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)
Traceback (most recent call last):
...
ValueError: n must be exact integer
>>> factorial)
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 ma(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
doc()
代码中定义了 factorial函数,在定义factorial函数下面有一大串三个双引号(“”“)括起来的说明文字,这块内容就是docstrings。而里面除了函数的作用描述之外,还有测试用例的样例和放回结果。这就是doctest能起作用的重点。
在__name__函数中即使调用doc()函数对本模块进行的测试。本例子执行结果是什么也没有返回,为啥? 恭喜,你的程序在指定的测试用例内,执行结果和预期相符,没有任何错误出现。 如果有错误出现的话,就会返回你测试用例报告,包含信息有执行的数量,成果的个数和失败的个数等。
当然也可以在成功时打印测试报告,这样就将测试的调用函数doc中的参数verbose=True。因为默认情况下verbose=False
doc(verbose=True)
这时执行结果为:
Trying:
[factorial(n) for n in range(6)]
Expecting:
[1, 1, 2, 6, 24, 120]
ok
Trying:
factorial(30)
Expecting:
265252859812191058636308480000000
ok
Trying:
factorial(-1)
Expecting:
Traceback (most recent call last):
...
ValueError: n must be >= 0
ok
Trying:
factorial)
Expecting:
Traceback (most recent call last):
...
ValueError: n must be exact integer
ok
Trying:
factorial)
Expecting:
265252859812191058636308480000000
ok
Trying:
factorial(1e100)
Expecting:
Traceback (most recent call last):
...
OverflowError: n too large
ok
1 items had no tests:
__main__
1 items passed all tests:
6 tests in __main__.factorial
6 tests in 2 items.
6 passed and 0 failed.
Test passed.
另外,如果使用交互式命令执行以上代码,也可以的。
$ python exam
$
这种情况也是没有输出测试报告的,怎么办?添加一个参数, -v
$ python exam -v
这样就可以看到上面的测试报告了。
加入我们的model文件中没有__name__函数,也就是没有调用doctest模块的测试函数,在交互式命令模式也是可以实现相同效果的。
python -m doctest -v exam
(2) 基于文档文件的测试
代码中插入适量的测试用例是可以接受的,但如果测试用例太复杂,以至于放到docstrings不合适,但有需要使用doctest进行测试,怎么办? doc()解决这个问题。上面的示例的docstrings放入exam中:
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
注意:这里面多类 >>> from example import factorial,这个是导入测试代模块和函数的。
这是测试代码改成
import doctest
doc("exam")
如果在交互式命令模式下执行且输出测试报告,使用下面的命令行。
python -m doctest -v exam
2. doctest工作机制
- docstring书写格式。
上面两种测试模式,都是基于docstring做的,只是存放位置不同而已,有点新瓶装旧酒的意味。可见关键还是这个“酒”如何。docstring格式有以下约束:
>>> # comments are ignored
>>> x = 12
>>> x
12
>>> if x == 13:
... print("yes")
... else:
... print("no")
... print("NO")
... print("NO!!!")
...
no
NO
NO!!!
>>>
- >>> ... 开头的行,表示执行的代码
- 输出不能说空白行,因为空白行代表输出结束。如果真是表示输出空白行的话,使用<BLANKLINE>
- 反斜线也是有益
3.doctest api
- doc函数
def testmod(m=None, name=None, globs=None, verbose=None,
report=True, optionflags=0, extraglobs=None,
raise_on_error=False, exclude_empty=False):
"""m=None, name=None, globs=None, verbose=None, report=True,
optionflags=0, extraglobs=None, raise_on_error=False,
exclude_empty=False
Test examples in docstrings in functions and classes reachable
from module m (or the current module if m is not supplied), starting
with m.__doc__.
Also test examples reachable from dict m.__test__ if it exists and is
not None. m.__test__ maps names to functions, classes and strings;
function and class docstrings are tested even if the name is private;
strings are tested directly, as if they were docstrings.
Return (#failures, #tests).
See help(doctest) for an overview.
Optional keyword arg "name" gives the name of the module; by default
use m.__name__.
Optional keyword arg "globs" gives a dict to be used as the globals
when executing examples; by default, use m.__dict__. A copy of this
dict is actually used for each docstring, so that each docstring's
examples start with a clean slate.
Optional keyword arg "extraglobs" gives a dictionary that should be
merged into the globals that are used to execute examples. By
default, no extra globals are used. This is new in 2.4.
Optional keyword arg "verbose" prints lots of stuff if true, prints
only failures if false; by default, it's true iff "-v" is in .
Optional keyword arg "report" prints a summary at the end when true,
else prints nothing at the end. In verbose mode, the summary is
detailed, else very brief (in fact, empty if all tests passed).
Optional keyword arg "optionflags" or's together module constants,
and defaults to 0. This is new in 2.3. Possible values (see the
docs for details):
DONT_ACCEPT_TRUE_FOR_1
DONT_ACCEPT_BLANKLINE
NORMALIZE_WHITESPACE
ELLIPSIS
SKIP
IGNORE_EXCEPTION_DETAIL
REPORT_UDIFF
REPORT_CDIFF
REPORT_NDIFF
REPORT_ONLY_FIRST_FAILURE
Optional keyword arg "raise_on_error" raises an exception on the
first unexpected exception or failure. This allows failures to be
post-mortem debugged.
Advanced tomfoolery: testmod runs methods of a local instance of
class doc, then merges the results into (or creates)
global Tester instance doc. Methods of doc
can be called directly too, if you want to do something unusual.
Passing report=0 to testmod is especially useful then, to delay
displaying a summary. Invoke doc.summarize(verbose)
when you're done fiddling.
"""
2.doc 函数
def 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):
"""
Test examples in the given file. Return (#failures, #tests).
Optional keyword arg "module_relative" specifies how filenames
should be interpreted:
- If "module_relative" is True (the default), then "filename"
specifies a module-relative path. By default, this path is
relative to the calling module's directory; but if the
"package" argument is specified, then it is relative to that
package. To ensure os-independence, "filename" should use
"/" characters to separate path segments, and should not
be an absolute path ., it may not begin with "/").
- If "module_relative" is False, then "filename" specifies an
os-specific path. The path may be absolute or relative (to
the current working directory).
Optional keyword arg "name" gives the name of the test; by default
use the file's basename.
Optional keyword argument "package" is a Python package or the
name of a Python package whose directory should be used as the
base directory for a module relative filename. If no package is
specified, then the calling module's directory is used as the base
directory for module relative filenames. It is an error to
specify "package" if "module_relative" is False.
Optional keyword arg "globs" gives a dict to be used as the globals
when executing examples; by default, use {}. A copy of this dict
is actually used for each docstring, so that each docstring's
examples start with a clean slate.
Optional keyword arg "extraglobs" gives a dictionary that should be
merged into the globals that are used to execute examples. By
default, no extra globals are used.
Optional keyword arg "verbose" prints lots of stuff if true, prints
only failures if false; by default, it's true iff "-v" is in .
Optional keyword arg "report" prints a summary at the end when true,
else prints nothing at the end. In verbose mode, the summary is
detailed, else very brief (in fact, empty if all tests passed).
Optional keyword arg "optionflags" or's together module constants,
and defaults to 0. Possible values (see the docs for details):
DONT_ACCEPT_TRUE_FOR_1
DONT_ACCEPT_BLANKLINE
NORMALIZE_WHITESPACE
ELLIPSIS
SKIP
IGNORE_EXCEPTION_DETAIL
REPORT_UDIFF
REPORT_CDIFF
REPORT_NDIFF
REPORT_ONLY_FIRST_FAILURE
Optional keyword arg "raise_on_error" raises an exception on the
first unexpected exception or failure. This allows failures to be
post-mortem debugged.
Optional keyword arg "parser" specifies a DocTestParser (or
subclass) that should be used to extract tests from the files.
Optional keyword arg "encoding" specifies an encoding that should
be used to convert the file to unicode.
Advanced tomfoolery: testmod runs methods of a local instance of
class doc, then merges the results into (or creates)
global Tester instance doc. Methods of doc
can be called directly too, if you want to do something unusual.
Passing report=0 to testmod is especially useful then, to delay
displaying a summary. Invoke doc.summarize(verbose)
when you're done fiddling.
"""
4.单元测试API
这方面同unitest相关性大,另做一篇介绍
5.高级API
对于一个文档解析流程需要 find, parse, run 和check excample 内容。对于流程中需要的对象分别由
DocTestFinder, DocParser, DoctestRunner和OutputChecher实现。
具体流程见下图:
list of:
+------+ +---------+
|module| --DocTestFinder-> | DocTest | --DocTestRunner-> results
+------+ | ^ +---------+ | ^ (printed)
| | | Example | | |
v | | ... | v |
DocTestParser | Example | OutputChecker
+---------+
4.应用场景
- j测试文档示例
- 回退测试
- 可执行的文档文字测试