Table of Contents generated with DocToc
-
每个测试单元应该关注于一个功能,并保证其正确性。
-
测试单元之间应该尽可能独立,也就是说可以独立运行,与顺序无关。
-
测试的速度应该尽可能快,过慢的测试速度会成为开发的瓶颈。对于耗费时间很长的重型测试,应该将其独立出来。
-
在集中编程前后都应该完整地运行一遍测试,以保证不会造成意外的破坏。
-
在编程过程中,如果需要中断工作,那么编写一个不能运行的测试对于恢复工作非常有帮助。
-
debug 的第一步就是写一个针对性的单元测试,虽然这做起来并不一定容易,但却非常有价值。
-
虽然 PEP8 提倡简短的命名,但在测试函数名称应该长而有意义。比如,编程中你可能使用
square()
甚至sqr()
这样的函数名称,但是在测试中你应该写成:test_square_of_number_2()
,test_square_negative_number()
使用Pylint来进行Python代码检测。Pylint官网
# install
$ pip3 install pylint
# usage
$ pylint example.py
# 之后会打印检测结果和得分
Python自带unittest标准库。在unittest中,通过assert开头的断言方法,来检查返回的结果是否符合预期
# 编写一个待测试的函数
def just_do_it(text):
return text.capitalize()
import unittest # Python的内置标准库,依赖于Java的JUnit
class TestCap(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_one_word(self):
text = 'duck'
result = just_do_it(text)
self.assertEqual(result, 'Duck')
def test_multiple_words(self):
text = 'a veritable flock of ducks'
result = just_do_it(text)
self.assertEqual(result, 'A Veritable Flock Of Duck')
if __name__ == '__main__':
unittest.main()
setUp()
方法会在每个测试方法执行之前执行,常用来进行一些初始化和分配外部资源的操作tearDown()
方法则在每个测试方法执行之后执行,常用来回收外部资源
断言方法
assertEqual(first, second, msg=None)
assertNotEqual(first, second, msg=None)
assertTrue(expr, msg=None)
assertFalse(expr, msg=None)
assertIs(first, second, msg=None)
assertIsNot(first, second, msg=None)
assertIsNone(expr, msg=None)
assertIsNotNone(expr, msg=None)
assertIn(first, second, msg=None)
assertNotIn(first, second, msg=None)
assertIsInstance(obj, cls, msg=None)
assertNotIsInstance(obj, cls, msg=None)
assertGreater(first, second, msg=None)
assertGreaterEqual(first, second, msg=None)
assertLess(first, second, msg=None)
assertLessEqual(first, second, msg=None)
又一个Python自带标准库doctest
使用这个包可以把测试写在文档字符串中,在测试的同时起到文档的作用。格式要求:
# 先>>> 函数调用
# 下一行输出预期的结果
"""
>>> fun()
result
"""
EXAMPLE:
import doctest
def list_generator(number):
"""
>>> list_generator(5)
[0, 1, 2, 3, 4]
"""
return [n for n in range(number)]
if __name__ == '__main__':
doctest.testmod()
运行Python文件,如果没有错误,则不会输出内容。现在把list_generator稍微改变一下:
import doctest
def list_generator(number):
"""
>>> list_generator(5)
[0, 1, 2, 3, 4]
"""
return [str(n) for n in range(number)]
if __name__ == '__main__':
doctest.testmod()
运行文件,输出如下内容:
**********************************************************************
File "example.py", line 5, in __main__.list_generator
Failed example:
list_generator(5)
Expected:
[0, 1, 2, 3, 4]
Got:
['0', '1', '2', '3', '4']
**********************************************************************
1 items had failures:
1 of 1 in __main__.list_generator
***Test Failed*** 1 failures.
By the way,如果你不想写:
if __name__ == '__main__':
doctest.testmod()
那么,在运行Python文件的时候,需要:
# python example.py 之前这样运行
$ python example.py -v # 现在这样运行才能进行代码测试
import pdb
pdb.set_trace() # 设置一个断点
# example
print('debugger begin')
pdb.set_trace()
for i in range(5):
print(i)
pdb.set_trace()
print('debugger end')
运行文件,会进入交互式debugger模式。在该模式下可以进行如下操作(仅列常用):
c
: 继续执行代码,直至下一个断点s
: 执行当前代码行,进入当前行调用的函数内部,并停在第一个能停的地方n
: 继续执行,到当前函数的下一行停止,或者当前行直接返回(单步跳过)w
: 显示当前正在执行的代码行的上下文信息a
: 打印当前函数的参数列表l
: 该命令后没有参数时,列出当前debug行附近的11行代码q
: 退出运行的代码
# 在Pdb的debug模式下
# 以当前行为中心,上下各列出5行代码。共计11行
(Pdb) l
(Pdb) l .
# 以第一个参数为起点,往后列出共计11行代码
(Pdb) l 2
# 以第一个参数为行数起点,第二个参数作为终止行数,列出期间的代码
(Pdb) l 2, 5
鉴于decorator可以人为的改变函数的行为,而可以不去影响它的内部逻辑,因此我们可以通过装饰器来增强函数行为,让它在执行前后打印出我们想要观测的数据。
from time import sleep
def decorate_fun(fun):
def new_fun(*args, **kwargs):
print('got args: {}, {}'.format(args, kwargs))
print('this is a fun\'s start')
fun(*args, **kwargs)
print('this is the fun\'s end')
return new_fun
@decorate_fun
def fun(number, start_number=0):
for n in range(number):
start_number += n
print(n)
sleep(1)
print('the final number is {}'.format(start_number))
print(fun.__name__) # new_fun
fun(5, start_number=5)
如上,通过装饰器修饰之后,之前的函数fun会被new_fun替代,被重写的函数名和注释文档。为了避免这个情况,可以使用functools.wraps
:
from functools import wraps
def decorate_fun(fun):
@wraps(fun)
def new_fun(*args, **kwargs):
print('got args: {}, {}'.format(args, kwargs))
print('this is a fun\'s start')
fun(*args, **kwargs)
print('this is the fun\'s end')
return new_fun
# def fun..
print(fun.__name__) # fun
$ pip3 install nose
nose与其他测试框架或者unittest相比,其最大的特色是不需要专门创建一个包含测试方法的类。任何名称中带有test的函数都会被执行测试。 除此以外,在项目目录下运行:
def test_example():
a = 1
b = 3
assert a + b is 4
$ nosetests
都会监测到你的测试方法并运行测试
nose会自动识别源文件,目录或包中的测试用例。任何符合正则表达式:
(?:^|[b_.-])[Tt]est
的类、函数、文件或目录,以及TestCase的子类都会被识别并执行。除了自动搜索测试以外,可以显式的指定文件、模块或函数:
$ nosetests only_test_this.py
$ nosetests test.module
通过断言进行防御性的编程、代码检查以及运行时对程序逻辑的检测等等。详见: Python 使用断言的最佳时机