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. 洛谷—— P3395 路障

    https://www.luogu.org/problem/show?pid=3395 题目背景 此题约为NOIP提高组Day1T1难度. 题目描述 B君站在一个n*n的棋盘上.最开始,B君站在(1, ...

  2. 洛谷—— P1605 迷宫

    P1605 迷宫 题目背景 迷宫 [问题描述] 给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过.给定起点坐标和 终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案.在 ...

  3. 针对JedisShardInfo中无法修改db的解决办法

    package com.ldr.bean; import java.lang.reflect.Field; import redis.clients.jedis.JedisShardInfo; pub ...

  4. BZOJ1016最小生成树计数 最小生成树 + 排列组合

    @[最小生成樹, 排列組合] Discription 现在给出了一个简单无向加权图.你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的 最小生成树.(如果两颗最小生成树中至少有一条边不 ...

  5. oracle-统计员工x

    1. SELECTe.depid,avg(s.bonussalary+s.basesalary) AS avgsal from employ e,salary s where e.employId=s ...

  6. KernelHacking

    https://kernelnewbies.org/KernelHacking-HOWTO/Debugging_Kernel

  7. 使用fiddler进行手机数据抓取

    使用fiddler进行手机数据抓取 学习了:https://blog.csdn.net/gld824125233/article/details/52588275 https://blog.csdn. ...

  8. [Binary Hacking] ABI and EABI

    Following are some general papers about ABI and EABI. Entrance https://en.wikipedia.org/wiki/Applica ...

  9. JAVA_MyEclipse如何加载Tomcat

          注意Tomcat不要放到Program Files这种有空格的路径下面!,下图所示是错误的      

  10. docker nginx镜像+phpfpm 镜像 组合配置 搭建 PHP+nginx 环境

    前言 在以往的容器环境部署中 运行环境 我们通常把 类似 apache nginx php 等 打包在一个镜像中 起一个容器. 这样做的好处是 方便 简单. 不便的地方是 如果PHP 需要扩展新的 相 ...