doctest是一个python标准库自带的轻量单元测试工具,适合实现一些简单的单元测试。它可以在docstring中寻找测试用例并执行,比较输出结果与期望值是否符合。

基本用法
使用doctest需要先在python的交互解释器中创建测试用例,并复制粘贴到docstring中即可。比如a.py内容如下:

def my_function(a, b):
"""
>>> my_function(2, 3)
6
>>> my_function('a', 3)
'aaa'
"""

return a * b
1
2
3
4
5
6
7
8
9
然后使用如下命令执行测试:

$ python -m doctest a.py -v
Trying:
my_function(2, 3)
Expecting:
6
ok
Trying:
my_function('a', 3)
Expecting:
'aaa'
ok
1 items had no tests:
a
1 items passed all tests:
2 tests in a.my_function
2 tests in 2 items.
2 passed and 0 failed.
Test passed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
doctest在docstring中寻找测试用例的时候,认为>>>是一个测试用例的开始,直到遇到空行或者下一个>>>,在两个测试用例之间有其他内容的话,会被doctest忽略(可以利用这个特性为测试用例编写一些注释)。

处理可变变量
测试过程中,有一些结果的部分是在不断变化的,比如时间、对象的ID等等。b.py内容如下:

class MyClass(object):
pass

def unpredictable(obj):
"""Returns a new list containing obj.

>>> unpredictable(MyClass())
[<b.MyClass object at 0x10055a2d0>]
"""
return [obj]
1
2
3
4
5
6
7
8
9
10
直接运行这个测试用例必然失败,因为对象在内存中的位置是不固定的,这个时候可以使用doctest的ELLIPSIS开关,并在需要忽略的地方用…代替。c.py的内容如下:

class MyClass(object):
pass

def unpredictable(obj):
"""Returns a new list containing obj.

>>> unpredictable(MyClass()) #doctest: +ELLIPSIS
[<c.MyClass object at 0x...>]
"""

return [obj]
1
2
3
4
5
6
7
8
9
10
11
测试结果如下:

$ python -m doctest c.py -v
Trying:
unpredictable(MyClass()) #doctest: +ELLIPSIS
Expecting:
[<c.MyClass object at 0x...>]
ok
2 items had no tests:
c
c.MyClass
1 items passed all tests:
1 tests in c.unpredictable
1 tests in 3 items.
1 passed and 0 failed.
Test passed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
期望值为空和交互器跨多行的情形
有的时候比如一个简单的赋值操作,是没有返回值的。另外如果有for循环等需要跨越多行的代码,也需要有正确的编写方式。d.py的内容如下:

def group_by_length(words):
"""Returns a dictionary grouping words into sets by length.

>>> grouped = group_by_length([ 'python', 'module', 'of', 'the', 'week' ])
>>> grouped == { 2:set(['of']),
... 3:set(['the']),
... 4:set(['week']),
... 6:set(['python', 'module']),
... }
True

"""
d = {}
for word in words:
s = d.setdefault(len(word), set())
s.add(word)
return d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
测试结果如下:

$ python -m doctest d.py -v
Trying:
grouped = group_by_length(['python', 'module', 'of', 'the', 'week'])
Expecting nothing
ok
Trying:
grouped == { 2: set(['of']),
3: set(['the']),
4: set(['week']),
6: set(['python', 'module'])
}
Expecting:
True
ok
1 items had no tests:
d
1 items passed all tests:
2 tests in d.group_by_length
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
处理Traceback
Traceback是一种特殊的可变变量,因为Traceback中的信息会随着系统平台、脚本文件位置的变化而变化,所以要匹配Traceback信息时,可以只写第一行Traceback (most recent call last):(或者Traceback (innermost last):)和最后一行异常类型及异常信息,忽略中间的路径信息等内容即可。e.py内容如下:

def this_raises():
"""This function always raises an exception.

>>> this_raises()
Traceback (most recent call last):
RuntimeError: here is the error
"""
raise RuntimeError('here is the error')
1
2
3
4
5
6
7
8
测试结果如下:

$ python -m doctest e.py -v
Trying:
this_raises()
Expecting:
Traceback (most recent call last):
RuntimeError: here is the error
ok
1 items had no tests:
e
1 items passed all tests:
1 tests in e.this_raises
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
把Traceback信息完全复制进来也没有关系,doctest在处理的时候其实只关注第一行和最后一行。

处理空白字符
有时输出中会包含空行、空格等空白字符,然而在doctest的期望结果中,空行是表示测试用例的结束,这个时候可以用<BLANKLINE>代表空行。f.py的内容如下:

def double_space(lines):
"""Prints a list of lines double-spaced.

>>> double_space(['Line one.', 'Line two.'])
Line one.
<BLANKLINE>
Line two.
<BLANKLINE>
"""
for l in lines:
print l
print
return
1
2
3
4
5
6
7
8
9
10
11
12
13
测试结果如下:

$ python -m doctest -v f.py
Trying:
double_space(['Line one.', 'Line two.'])
Expecting:
Line one.
<BLANKLINE>
Line two.
<BLANKLINE>
ok
1 items had no tests:
f
1 items passed all tests:
1 tests in f.double_space
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
另外一种需要处理空白的场景是期望值附近可能在复制粘贴过程中加入了不必要的空格字符,这个时候实际结果和期望结果肯定有差异,但是在测试报告中这种差异并不能一眼看出来。这种情况下可以使用doctest的REPORT_NDIFF选项,比如g.py的内容如下(6后面有一个多余的空格):

def my_function(a, b):
"""
>>> my_function(2, 3)
6
>>> my_function('a', 3)
'aaa'
"""
return a * b
1
2
3
4
5
6
7
8
测试结果如下:

Trying:
my_function(2, 3)
Expecting:
6
**********************************************************************
File "g.py", line 3, in g.my_function
Failed example:
my_function(2, 3)
Expected:
6
Got:
6
Trying:
my_function('a', 3)
Expecting:
'aaa'
ok
1 items had no tests:
g
**********************************************************************
1 items had failures:
1 of 2 in g.my_function
2 tests in 2 items.
1 passed and 1 failed.
***Test Failed*** 1 failures.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
仅凭肉眼很难观察到到底是哪里有差异,使用REPORT_NDIFF:

def my_function(a, b):
"""
>>> my_function(2, 3) #doctest: +REPORT_NDIFF
6
>>> my_function('a', 3) #doctest: +REPORT_NDIFF
'aaa'
"""
return a * b
1
2
3
4
5
6
7
8
测试结果如下:

Trying:
my_function(2, 3) #doctest:+REPORT_NDIFF
Expecting:
6
**********************************************************************
File "g.py", line 3, in g.my_function
Failed example:
my_function(2, 3) #doctest:+REPORT_NDIFF
Differences (ndiff with -expected +actual):
- 6
? -
+ 6
Trying:
my_function('a', 3) #doctest:+REPORT_NDIFF
Expecting:
'aaa'
ok
1 items had no tests:
g
**********************************************************************
1 items had failures:
1 of 2 in g.my_function
2 tests in 2 items.
1 passed and 1 failed.
***Test Failed*** 1 failures.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
有时,为了增强测试代码可读性,需要在期望值中加入一些空白字符,比如对于某些数据结构来说,分布在多行无疑可读性更强,这个时候可以使用doctest的NORMALIZE_WHITESPACE选项,比如h.py内容如下:

def my_function(dic):
'''
>>> my_function({1:2, 3:4}) #doctest:+NORMALIZE_WHITESPACE
{1: 2,
3: 4}
'''
return dic
1
2
3
4
5
6
7
测试结果如下:

$ python -m doctest -v h.py
Trying:
my_function({1:2, 3:4}) #doctest:+NORMALIZE_WHITESPACE
Expecting:
{1: 2,
3: 4}
ok
1 items had no tests:
h
1 items passed all tests:
1 tests in h.my_function
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
需要注意的是,使用NORMALIZE_WHITESPACE时,如果在实际输出没有空白字符的位置添加了空白,测试是无法通过的,比如上面的例子,在“{”右边并没有空格,如果h.py中添加了空格,会导致测试失败:

def my_function(dic):
'''
>>> my_function({1:2, 3:4}) #doctest:+NORMALIZE_WHITESPACE
{ 1: 2,
3: 4}
'''
return dic
1
2
3
4
5
6
7
失败的测试结果如下:

$ python -m doctest -v h.py
Trying:
my_function({1:2, 3:4}) #doctest:+NORMALIZE_WHITESPACE
Expecting:
{ 1: 2,
3: 4}
**********************************************************************
File "h.py", line 3, in h.my_function
Failed example:
my_function({1:2, 3:4}) #doctest:+NORMALIZE_WHITESPACE
Expected:
{ 1: 2,
3: 4}
Got:
{1: 2, 3: 4}
1 items had no tests:
h
**********************************************************************
1 items had failures:
1 of 1 in h.my_function
1 tests in 2 items.
0 passed and 1 failed.
***Test Failed*** 1 failures.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
测试用例的位置
目前所有的例子都是在函数的docstring中,实际上doctest的测试用例可以放在任何docstring中,比如模块、类、类的方法等的docstring中。
虽然doctest可以存在于那么多位置,但是这些测试用例在helpdoc中是可见的,为了在helpdoc中不可见,可以使用一个叫做__test__的模块级的字典变量,字典的键是一个字符串,用来表示测试用例集的名字,字典的值是一个包含了doctest的测试用例的字符串、模块、类或者函数,比如i.py和my_test_module.py内容分别如下:

# i.py
import my_test_module

def my_test_func():
'''
>>> my_func(['a', 'b'], 2)
['a', 'b', 'a', 'b']
'''
pass

class my_test_class(object):
'''
>>> my_func("lmz", 3)
'lmzlmzlmz'
'''
pass

def my_func(a, b):
'''
Returns a multiply b times.
'''
return a * b

__test__ = {
'function': my_test_func,
'class': my_test_class,
'module': my_test_module,
'string': '''
>>> my_func(13, 4)
52
'''
}

#my_test_module.py
'''
>>> my_func(2.0, 1)
2.0
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
测试结果如下:

$ python -m doctest -v i.py
Trying:
my_func(2.0, 1)
Expecting:
2.0
ok
Trying:
my_func(13, 4)
Expecting:
52
ok
Trying:
my_func("lmz", 3)
Expecting:
'lmzlmzlmz'
ok
Trying:
my_func(['a', 'b'], 2)
Expecting:
['a', 'b', 'a', 'b']
ok
2 items had no tests:
i
i.my_func
4 items passed all tests:
1 tests in i.__test__.module
1 tests in i.__test__.string
1 tests in i.my_test_class
1 tests in i.my_test_func
4 tests in 6 items.
4 passed and 0 failed.
Test passed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
从其他格式的帮助文档中进行测试
比如使用reStructuredText帮助文档进行测试,如下是j.py和j.rst的内容:

#j.rst
===============================
How to use doctest in help doc
===============================

This library is very simple, since it only has one function called
``my_function()``.

Numbers
=======

``my_function()`` returns the product of its arguments. For numbers,
that value is equivalent to using the ``*`` operator.

::

>>> from j import my_function
>>> my_function(2, 3)
6

It also works with floating point values.

::

>>> my_function(2.0, 3)
6.0

Non-Numbers
===========

Because ``*`` is also defined on data types other than numbers,
``my_function()`` works just as well if one of the arguments is a
string, list, or tuple.

::

>>> my_function('a', 3)
'aaa'

>>> my_function(['A', 'B', 'C'], 2)
['A', 'B', 'C', 'A', 'B', 'C']

#j.py
def my_function(a, b):
"""
Returns a*b
"""
return a * b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
注意测试开始需要先导入模块,执行测试后输出如下:

$ python -m doctest -v j.rst
Trying:
from j import my_function
Expecting nothing
ok
Trying:
my_function(2, 3)
Expecting:
6
ok
Trying:
my_function(2.0, 3)
Expecting:
6.0
ok
Trying:
my_function('a', 3)
Expecting:
'aaa'
ok
Trying:
my_function(['A', 'B', 'C'], 2)
Expecting:
['A', 'B', 'C', 'A', 'B', 'C']
ok
1 items passed all tests:
5 tests in j.rst
5 tests in 1 items.
5 passed and 0 failed.
Test passed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
PS:reStructuredText是Python的一款文档工具,上面j.rst转换后的效果是这样的:

更方便的执行测试
目前为止都是使用doctest的命令行形式执行测试,如果我们自己开发的package包含很多文件的话,那么敲命令会很麻烦,下面来看看其他执行doctest测试用例的方法。
1.从模块执行
比如k.py的内容如下:

def my_function(a, b):
'''
>>> my_function(2, 3)
6

>>> my_function('lmz', 2)
'lmzlmz'
'''
return a * b

if __name__ == '__main__':
import doctest
doctest.testmod()
1
2
3
4
5
6
7
8
9
10
11
12
13
这样直接使用如下命令就可以运行测试用例:

MarsLoo:learnspace marsloo$ python k.py -v
Trying:
my_function(2, 3)
Expecting:
6
ok
Trying:
my_function('lmz', 2)
Expecting:
'lmzlmz'
ok
1 items had no tests:
__main__
1 items passed all tests:
2 tests in __main__.my_function
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
testmod的第一个参数可以是一个模块的名字,这样我们可以再写一个类似测试套的程序,将要测试的模块import进来,然后对其调用doctest.testmod(模块名)。

2.从文件执行
类似从模块执行,doctest模块还有一个testfile(filename)的方法。

3.与unittest配合使用
doctest模块的DocTestSuite和DocFileSuite函数能够与unittest对接,比如:

import unittest, doctest
import j

suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite(j))
suite.addTest(doctest.DocFileSuite('j.rst'))

runner = unittest.TextTestRunner(verbosity = 2)
runner.run(suite)
1
2
3
4
5
6
7
8
9
关于变量空间
doctest在执行每个测试用例的时候,会为他们创建隔离的变量空间,这样在测试用例A中创建的变量,在测试用例B中是访问不到的,比如:
# n.py
class MyClass(object):
def case_A(self):
'''
>>> var = 'a'
>>> 'var' in globals()
True
'''
pass

def case_B(self):
'''
>>> 'var' in globals()
False
'''
pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
执行测试后结果如下:

MarsLoo:learnspace marsloo$ python -m doctest n.py -v
Trying:
var = 'a'
Expecting nothing
ok
Trying:
'var' in globals()
Expecting:
True
ok
Trying:
'var' in globals()
Expecting:
False
ok
2 items had no tests:
n
n.MyClass
2 items passed all tests:
2 tests in n.MyClass.case_A
1 tests in n.MyClass.case_B
3 tests in 4 items.
3 passed and 0 failed.
Test passed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
如果实在想要在测试用例之间交换数据,可以在脚本的全局范围内放置一个字典,比如:

# o.py
__inner_dict__ = {}

class MyClass(object):
def case_A(self):
'''
>>> global __inner_dict__
>>> __inner_dict__['var'] = 'a'
>>> 'var' in __inner_dict__
True
'''
pass

def case_B(self):
'''
>>> 'var' in __inner_dict__
True
'''
pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
执行测试后结果如下:

MarsLoo:learnspace marsloo$ python -m doctest o.py -v
Trying:
global __inner_dict__
Expecting nothing
ok
Trying:
__inner_dict__['var'] = 'a'
Expecting nothing
ok
Trying:
'var' in __inner_dict__
Expecting:
True
ok
Trying:
'var' in __inner_dict__
Expecting:
True
ok
2 items had no tests:
o
o.MyClass
2 items passed all tests:
3 tests in o.MyClass.case_A
1 tests in o.MyClass.case_B
4 tests in 4 items.
4 passed and 0 failed.
Test passed.
---------------------
作者:Mars_Loo
来源:CSDN
原文:https://blog.csdn.net/a464057216/article/details/51866748
版权声明:本文为博主原创文章,转载请附上博文链接!

Python的单元测试工具——doctest的更多相关文章

  1. Python单元测试工具doctest和unittest

    Python标准库包含两个测试工具. doctest:一个简单的模块,为检查文档而设计,但也适合用来编写单元测试. unittest:一个通用的测试框架. 一.使用doctest进行单元测试 创建文件 ...

  2. 一种数据与逻辑分离的Python单元测试工具

    一种数据与逻辑分离的Python单元测试工具 几个概念 TestCase TestCase是一个完整的测试单元,最小的测试执行实体,就是我们常说的测试用例. TestSuite 以某种特性将测试用例组 ...

  3. python基础——单元测试

    python基础——单元测试 如果你听说过“测试驱动开发”(TDD:Test-Driven Development),单元测试就不陌生. 单元测试是用来对一个模块.一个函数或者一个类来进行正确性检验的 ...

  4. Python测试 ——开发工具库

    Web UI测试自动化 splinter - web UI测试工具,基于selnium封装. selenium - web UI自动化测试. mechanize- Python中有状态的程序化Web浏 ...

  5. Openstack单元测试工具简单说明

    一.Openstack 的单元测试工具介绍 1.unittest unittest: 是 Python 的标准库,提供了最基本的单元测试功能,包括 单元测试运行器(简称runner) 和 单元测试框架 ...

  6. OpenStack基础知识-单元测试工具介绍

    针对以前学的内容的一个简单整理 1.单元测试工具介绍 unittest: 是 Python 的标准库,提供了最基本的单元测试功能,包括 单元测试运行器(简称runner) 和 单元测试框架.项目的单元 ...

  7. Openstack_单元测试工具 tox

    目录 目录 扩展阅读 Openstack 的单元测试工具 单元测试工具使用流程 tox toxini 参考文章 扩展阅读 Python Mock的入门 Openstack 的单元测试工具 unitte ...

  8. python测试开发工具库汇总(转载)

    Web UI测试自动化 splinter - web UI测试工具,基于selnium封装. selenium - web UI自动化测试. mechanize- Python中有状态的程序化Web浏 ...

  9. Python集成开发工具(IDE)推荐

    1.7 Python集成开发工具(IDE)推荐 1.7.1 Notepad++ Notepad++是Windows操作系统下的一套文本编辑器(软件版权许可证: GPL),有完整的中文化接口及支持多国语 ...

随机推荐

  1. [Bzoj3205][Apio2013]机器人(斯坦纳树)(bfs)

    3205: [Apio2013]机器人 Time Limit: 15 Sec  Memory Limit: 128 MBSubmit: 977  Solved: 230[Submit][Status] ...

  2. 洛谷 P1710 地铁涨价

    题目背景 本题开O2优化,请注意常数 题目描述 博艾市除了有海底高铁连接中国大陆.台湾与日本,市区里也有很成熟的轨道交通系统.我们可以认为博艾地铁系统是一个无向连通图.博艾有N个地铁站,同时有M小段地 ...

  3. Nginx配置文件语法教程

    Nginx的配置文件在一开始可能真的不太好理解,就像当初开始使用Apache那样,像JSON但却不是.可以说是Nginx的一种专门语言,仅为Nginx服务的. 市面上基本都是写了一点不写一点的教程,基 ...

  4. Go -- NSQ topic和channel的区别

    topic:一个可供订阅的话题.channel:属于topic的下一级,一个topic可以有多个channel. 举个例子:topic:比做一个广播,如交通广播.打开收音机,你可以换很多频率,如果换到 ...

  5. VMware Workstation 虚拟机设置连接U盘

    首先确保主机有开启"VMware USB Arbitration Service"服务,而且在执行中. 如图:(我的系统是win8.1 ) 在VMware Workstation虚 ...

  6. 使用FMDB多线程訪问数据库,及database is locked的问题

    今天最终攻克了多线程同一时候訪问数据库时,报数据库锁定的问题.错误信息是: Unknown error finalizing or resetting statement (5: database i ...

  7. linux下查看网卡信息的命令

    rhel 内核版本号信息: [root@hvrhub ~]# uname -a Linux hvrhub 2.6.18-308.el5 #1 SMP Fri Jan 27 17:17:51 EST 2 ...

  8. LeetCode ||& Word Break && Word Break II(转)——动态规划

    一. Given a string s and a dictionary of words dict, determine if s can be segmented into a space-sep ...

  9. VC中常见API函数使用方法(经验版)

    ***********************************************声明*************************************************** ...

  10. 为什么说JAVA中要慎重使用继承 C# 语言历史版本特性(C# 1.0到C# 8.0汇总) SQL Server事务 事务日志 SQL Server 锁详解 软件架构之 23种设计模式 Oracle与Sqlserver:Order by NULL值介绍 asp.net MVC漏油配置总结

    为什么说JAVA中要慎重使用继承   这篇文章的主题并非鼓励不使用继承,而是仅从使用继承带来的问题出发,讨论继承机制不太好的地方,从而在使用时慎重选择,避开可能遇到的坑. JAVA中使用到继承就会有两 ...