http://blog.csdn.net/pipisorry/article/details/39123651
一、使用doctest\unitest进行python代码测试
对于开发者来说,最实用的帮助莫过于帮助他们编写代码文档了。pydoc模块可以根据源代码中的docstrings为任何可导入模块生成格式良好的文档。
Python包含了两个测试框架来自动测试代码以及验证代码的正确性:
1)doctest模块,该模块可以从源代码或独立文件的例子中抽取出测试用例。
2)unittest模块,该模块是一个全功能的自动化测试框架,该框架提供了对测试准备(test fixtures), 预定义测试集(predefined test suite)以及测试发现(test discovery)的支持。
unitest
通用测试框架[python单元测试unittest]
doctest
简单一点的模块,用于检查文档的,对于编写的单元测试也很在行。
doctest查找和执行交互式Python会话,检验其符合预期,常用场景如下:
Doctest比unittest简单,没有API,是大型测试框架的有机补充。不过doctest没有fixture,不适合复杂的场景。
[doctest — Test interactive Python examples]
[doctest – Testing through documentation]
def unit_test(x): ''' >>> unit_test(3) 6 ''' return x*x import doctest if __name__ == '__main__': doctest.testmod()#my_math.py
def square(x):
'''''
Squares a number and returns the results.
>>> square(2)
4
>>> square(3)
9
'''
return x*x
if __name__ == '__main__':
import doctest, my_math
doctest.testmod(my_math)
成功运行情况
[root@pdns0 PythonStudy]# python my_math.py -v
Trying:
square(2)
Expecting:
4
ok
Trying:
square(3)
Expecting:
9
ok
1 items had no tests:
my_math
1 items passed all tests:
2 tests in my_math.square
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
把x*x改成x**x,得到错误运行情况:
[root@pdns0 PythonStudy]# python my_math.py -v
Trying:
square(2)
Expecting:
4
ok
Trying:
square(3)
Expecting:
9
**********************************************************************
File "/home/liqing/PythonStudy/my_math.py", line 8, in my_math.square
Failed example:
square(3)
Expected:
9
Got:
27
1 items had no tests:
my_math
**********************************************************************
1 items had failures:
1 of 2 in my_math.square
2 tests in 2 items.
1 passed and 1 failed.
***Test Failed*** 1 failures
Note:
如果函数,类或者是模块的第一行是一个字符串,那么这个字符串就是一个文档字符串。可以认为包含文档字符串是一个良好的编程习惯,这是因为这些字符串可以给Python程序开发工具提供一些信息。比如,help()命令能够检测文档字符串,Python相关的IDE也能够进行检测文档字符串的工作。由于程序员倾向于在交互式shell中查看文档字符串,所以最好将这些字符串写的简短一些。例如
# mult.py class Test: """ >>> a=Test(5) >>> a.multiply_by_2() 10 """ def __init__(self, number): self._number=number def multiply_by_2(self): return self._number*2在编写文档时,一个常见的问题就是如何保持文档和实际代码的同步。例如,程序员也许会修改函数的实现,但是却忘记了更新文档。针对这个问题,我们可以使用doctest模块。doctest模块收集文档字符串,并对它们进行扫描,然后将它们作为测试进行执行。为了使用doctest模块,我们通常会新建一个用于测试的独立的模块。例如,如果前面的例子 Test class包含在文件 mult.py中,那么,你应该新建一个 testmult.py文件用来测试,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# testmult.py
import
mult, doctest
doctest.testmod(mult, verbose
=
True
)
# Trying:
# a=Test(5)
# Expecting nothing
# ok
# Trying:
# a.multiply_by_2()
# Expecting:
# 10
# ok
# 3 items had no tests:
# mult
# mult.Test.__init__
# mult.Test.multiply_by_2
# 1 items passed all tests:
# 2 tests in mult.Test
# 2 tests in 4 items.
# 2 passed and 0 failed.
# Test passed.
|
在这段代码中,doctest.testmod(module)会执行特定模块的测试,并且返回测试失败的个数以及测试的总数目。如果所有的测试都通过了,那么不会产生任何输出。否则的话,你将会看到一个失败报告,用来显示期望值和实际值之间的差别。如果你想看到测试的详细输出,你可以使用testmod(module, verbose=True).
如果不想新建一个单独的测试文件的话,那么另一种选择就是在文件末尾包含相应的测试代码:
1
2
3
|
if
__name__
=
=
'__main__'
:
import
doctest
doctest.testmod()
|
如果想执行这类测试的话,我们可以通过-m选项调用doctest模块。通常来讲,当执行测试的时候没有任何的输出。如果想查看详细信息的话,可以加上-v选项。
1
|
$ python
-
m doctest
-
v mult.py
|
如果想更加彻底地对程序进行测试,我们可以使用unittest模块。通过单元测试,开发者可以为构成程序的每一个元素(例如,独立的函数,方法,类以及模块)编写一系列独立的测试用例。当测试更大的程序时,这些测试就可以作为基石来验证程序的正确性。当我们的程序变得越来越大的时候,对不同构件的单元测试就可以组合起来成为更大的测试框架以及测试工具。这能够极大地简化软件测试的工作,为找到并解决软件问题提供了便利。
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
|
# splitter.py
import
unittest
def
split(line, types
=
None
, delimiter
=
None
):
"""Splits a line of text and optionally performs type conversion.
...
"""
fields
=
line.split(delimiter)
if
types:
fields
=
[ ty(val)
for
ty,val
in
zip
(types,fields) ]
return
fields
class
TestSplitFunction(unittest.TestCase):
def
setUp(
self
):
# Perform set up actions (if any)
pass
def
tearDown(
self
):
# Perform clean-up actions (if any)
pass
def
testsimplestring(
self
):
r
=
split(
'GOOG 100 490.50'
)
self
.assertEqual(r,[
'GOOG'
,
'100'
,
'490.50'
])
def
testtypeconvert(
self
):
r
=
split(
'GOOG 100 490.50'
,[
str
,
int
,
float
])
self
.assertEqual(r,[
'GOOG'
,
100
,
490.5
])
def
testdelimiter(
self
):
r
=
split(
'GOOG,100,490.50'
,delimiter
=
','
)
self
.assertEqual(r,[
'GOOG'
,
'100'
,
'490.50'
])
# Run the unittests
if
__name__
=
=
'__main__'
:
unittest.main()
#...
#----------------------------------------------------------------------
#Ran 3 tests in 0.001s
#OK
|
在使用单元测试时,我们需要定义一个继承自unittest.TestCase的类。在这个类里面,每一个测试都以方法的形式进行定义,并都以test打头进行命名——例如,’testsimplestring‘,’testtypeconvert‘以及类似的命名方式(有必要强调一下,只要方法名以test打头,那么无论怎么命名都是可以的)。在每个测试中,断言可以用来对不同的条件进行检查。
测试标准输出的例子:
假如你在程序里有一个方法,这个方法的输出指向标准输出(sys.stdout)。这通常意味着是往屏幕上输出文本信息。如果你想对你的代码进行测试来证明这一点,只要给出相应的输入,那么对应的输出就会被显示出来。
1
2
3
4
5
|
# url.py
def
urlprint(protocol, host, domain):
url
=
'{}://{}.{}'
.
format
(protocol, host, domain)
print
(url)
|
内置的print函数在默认情况下会往sys.stdout发送输出。为了测试输出已经实际到达,你可以使用一个替身对象对其进行模拟,并且对程序的期望值进行断言。unittest.mock模块中的patch()方法可以只在运行测试的上下文中才替换对象,在测试完成后就立刻返回对象原始的状态。下面是urlprint()方法的测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#urltest.py
from
io
import
StringIO
from
unittest
import
TestCase
from
unittest.mock
import
patch
import
url
class
TestURLPrint(TestCase):
def
test_url_gets_to_stdout(
self
):
protocol
=
'http'
host
=
'www'
domain
=
'example.com'
expected_url
=
'{}://{}.{}\n'
.
format
(protocol, host, domain)
with patch(
'sys.stdout'
, new
=
StringIO()) as fake_out:
url.urlprint(protocol, host, domain)
self
.assertEqual(fake_out.getvalue(), expected_url)
|
urlprint()函数有三个参数,测试代码首先给每个参数赋了一个假值。变量expected_url包含了期望的输出字符串。为了能够执行测试,我们使用了unittest.mock.patch()方法作为上下文管理器,把标准输出sys.stdout替换为了StringIO对象,这样发送的标准输出的内容就会被StringIO对象所接收。变量fake_out就是在这一过程中所创建出的模拟对象,该对象能够在with所处的代码块中所使用,来进行一系列的测试检查。当with语句完成时,patch方法能够将所有的东西都复原到测试执行之前的状态,就好像测试没有执行一样,而这无需任何额外的工作。但对于某些Python的C扩展来讲,这个例子却显得毫无意义,这是因为这些C扩展程序绕过了sys.stdout的设置,直接将输出发送到了标准输出上。这个例子仅适用于纯Python代码的程序(如果你想捕获到类似C扩展的输入输出,那么你可以通过打开一个临时文件然后将标准输出重定向到该文件的技巧来进行实现)。
二、使用nose进行python代码测试
推荐使用nose
或是py.test
,它们基本上是类似的。
使用nose进行测试的例子
在一个以test_
开头的文件中的所有以test_
开头的函数,都会被调用
1
2
|
def
test_equality():
assert
True
=
=
False
|
不出所料,当运行nose的时候,我们的测试没有通过。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
(
test
)jhaddad@jons-mac-pro ~VIRTUAL_ENV
/src
$ nosetests
F
======================================================================
FAIL: test_nose_example.test_equality
----------------------------------------------------------------------
Traceback (most recent call last):
File
"/Users/jhaddad/.virtualenvs/test/lib/python2.7/site-packages/nose/case.py"
, line 197,
in
runTest
self.
test
(*self.arg)
File
"/Users/jhaddad/.virtualenvs/test/src/test_nose_example.py"
, line 3,
in
test_equality
assert True == False
AssertionError
----------------------------------------------------------------------
|
nose.tools中同样也有一些便捷的方法可以调用
1
2
3
|
from
nose.tools
import
assert_true
def
test_equality():
assert_true(
False
)
|
想使用更加类似JUnit的方法:
1
2
3
4
5
6
7
8
9
10
|
from
nose.tools
import
assert_true
from
unittest
import
TestCase
class
ExampleTest(TestCase):
def
setUp(
self
):
# setUp & tearDown are both available
self
.blah
=
False
def
test_blah(
self
):
self
.assertTrue(
self
.blah)
|
开始测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
(
test
)jhaddad@jons-mac-pro ~VIRTUAL_ENV
/src
$ nosetests
F
======================================================================
FAIL: test_blah (test_nose_example.ExampleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File
"/Users/jhaddad/.virtualenvs/test/src/test_nose_example.py"
, line 11,
in
test_blah
self.assertTrue(self.blah)
AssertionError: False is not
true
----------------------------------------------------------------------
Ran 1
test
in
0.003s
FAILED (failures=1)
|
卓越的Mock库包含在Python 3 中,但是如果你在使用Python 2,可以使用pypi来获取。这个测试将进行一个远程调用,但是这次调用将耗时10s。这个例子显然是人为捏造的。我们使用mock来返回样本数据而不是真正的进行调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import
mock
from
mock
import
patch
from
time
import
sleep
class
Sweetness(
object
):
def
slow_remote_call(
self
):
sleep(
10
)
return
"some_data"
# lets pretend we get this back from our remote api call
def
test_long_call():
s
=
Sweetness()
result
=
s.slow_remote_call()
assert
result
=
=
"some_data"
|
当然,我们的测试需要很长的时间。
1
2
3
4
5
|
(
test
)jhaddad@jons-mac-pro ~VIRTUAL_ENV
/src
$ nosetests test_mock.py
Ran 1
test
in
10.001s
OK
|
太慢了!因此我们会问自己,我们在测试什么?我们需要测试远程调用是否有用,还是我们要测试当我们获得数据后要做什么?大多数情况下是后者。让我们摆脱这个愚蠢的远程调用吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import
mock
from
mock
import
patch
from
time
import
sleep
class
Sweetness(
object
):
def
slow_remote_call(
self
):
sleep(
10
)
return
"some_data"
# lets pretend we get this back from our remote api call
def
test_long_call():
s
=
Sweetness()
with patch.
object
(s,
"slow_remote_call"
, return_value
=
"some_data"
):
result
=
s.slow_remote_call()
assert
result
=
=
"some_data"
|
好吧,让我们再试一次:
1
2
3
4
5
6
|
(
test
)jhaddad@jons-mac-pro ~VIRTUAL_ENV
/src
$ nosetests test_mock.py
.
----------------------------------------------------------------------
Ran 1
test
in
0.001s
OK
|
好多了。记住,这个例子进行了荒唐的简化。就我个人来讲,我仅仅会忽略从远程系统的调用,而不是我的数据库调用。
nose-progressive是一个很好的模块,它可以改善nose的输出,让错误在发生时就显示出来,而不是留到最后。如果你的测试需要花费一定的时间,那么这是件好事。
pip install nose-progressive
并且在你的nosetests
中添加--with-progressive
ref:doctest样例
Python模块——doctest