|
|
|
@ -10,4 +10,185 @@
|
|
|
|
|
5.在测试驱动编程的理念中,首先程序员要编写测试程序,然后编写可以通过测试的程序。测试程序就是程序的需求说明,它能够帮助程序员在开发程序时,不偏离需求。TTD[Test-Driven Development]最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。
|
|
|
|
|
|
|
|
|
|
# 怎么编写单元测试?
|
|
|
|
|
对于Python代码而言,常用的测试工具有doctest和unittest。doctest是简单一些的模块,是检测文档用的。doctet.test_mod函数从一个模块中读取所有文档字符串,找出所有看起来像是在交互式解释器中输入的例子的文本,之后检查例子是否符合实际要求。在实际工作中,为python写单元测试时更加强大和常用的模块是unittest模块,unittest基于Java的流行测试框架Junit,通过使用unittest我们可以以结构化的方式编写大型而且周详的测试集。
|
|
|
|
|
对于Python代码而言,常用的测试工具有doctest和unittest。doctest是简单一些的模块,是检测文档用的。doctet.test_mod函数从一个模块中读取所有文档字符串,找出所有看起来像是在交互式解释器中输入的例子的文本,之后检查例子是否符合实际要求。在实际工作中,为python写单元测试时更加强大和常用的模块是unittest模块,unittest基于Java的流行测试框架Junit,通过使用unittest我们可以以结构化的方式编写大型而且周详的测试集。
|
|
|
|
|
|
|
|
|
|
# unittest模块
|
|
|
|
|
unittest 模块使用的模式有三种,如下:
|
|
|
|
|
|
|
|
|
|
## 通过unittest.main()来执行测试用例的方式:
|
|
|
|
|
import unittest
|
|
|
|
|
|
|
|
|
|
class UCTestCase(unittest.TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
#测试前需执行的操作
|
|
|
|
|
.....
|
|
|
|
|
def tearDown(self):
|
|
|
|
|
#测试用例执行完后所需执行的操作
|
|
|
|
|
.....
|
|
|
|
|
|
|
|
|
|
# 测试用例1
|
|
|
|
|
def testCreateFolder(self):
|
|
|
|
|
#具体的测试脚本
|
|
|
|
|
......
|
|
|
|
|
# 测试用例2
|
|
|
|
|
def testDeleteFolder(self):
|
|
|
|
|
#具体的测试脚本
|
|
|
|
|
......
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main()
|
|
|
|
|
|
|
|
|
|
## 通过testsuit来执行测试用例的方式:
|
|
|
|
|
import unittest
|
|
|
|
|
# 执行测试的类
|
|
|
|
|
class UCTestCase(unittest.TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
#测试前需执行的操作
|
|
|
|
|
.....
|
|
|
|
|
def tearDown(self):
|
|
|
|
|
#测试用例执行完后所需执行的操作
|
|
|
|
|
.....
|
|
|
|
|
# 测试用例1
|
|
|
|
|
def testCreateFolder(self):
|
|
|
|
|
#具体的测试脚本
|
|
|
|
|
......
|
|
|
|
|
def testDeleteFolder(self):
|
|
|
|
|
# 具体的测试脚本
|
|
|
|
|
If __name__ == "__main__":
|
|
|
|
|
# 构造测试集, 添加测试用例
|
|
|
|
|
suite = unittest.TestSuite()
|
|
|
|
|
suite.addTest(UC7TestCase("testCreateFolder"))
|
|
|
|
|
suite.addTest(UC7TestCase("testDeleteFolder"))
|
|
|
|
|
#执行测试, 构造runner。
|
|
|
|
|
runner = unittest.TextTestRunner()
|
|
|
|
|
runner.run(suite)
|
|
|
|
|
|
|
|
|
|
## 通过testloader运行测试集:
|
|
|
|
|
import unittest
|
|
|
|
|
class TestCase1(unittest.TestCase):
|
|
|
|
|
#def setUp(self):
|
|
|
|
|
#def tearDown(self):
|
|
|
|
|
def testCase1(self):
|
|
|
|
|
print 'aaa'
|
|
|
|
|
def testCase2(self):
|
|
|
|
|
print 'bbb'
|
|
|
|
|
class TestCase2(unittest.TestCase):
|
|
|
|
|
#def setUp(self):
|
|
|
|
|
#def tearDown(self):
|
|
|
|
|
def testCase1(self):
|
|
|
|
|
print 'aaa1'
|
|
|
|
|
def testCase2(self):
|
|
|
|
|
print 'bbb1'
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
#此用法可以同时测试多个类
|
|
|
|
|
suite1=unittest.TestLoader().loadTestsFromTestCase(TestCase1)
|
|
|
|
|
suite2=unittest.TestLoader().loadTestsFromTestCase(TestCase2)
|
|
|
|
|
suite = unittest.TestSuite([suite1, suite2])
|
|
|
|
|
unittest.TextTestRunner(verbosity=2).run(suite)
|
|
|
|
|
|
|
|
|
|
# Mock和MagicMock
|
|
|
|
|
在单元测试进行的同时,就离不开mock模块的存在,初次接触这个概念的时候会有这样的疑问:把要测的东西都模拟掉了还测试什么呢?
|
|
|
|
|
但在实际生产中的项目是非常复杂的,对其进行单元测试的时候,会遇到以下问题:
|
|
|
|
|
•接口的依赖
|
|
|
|
|
•外部接口调用
|
|
|
|
|
•测试环境非常复杂
|
|
|
|
|
单元测试应该只针对当前单元进行测试, 所有的内部或外部的依赖应该是稳定的, 已经在别处进行测试过的。使用mock 就可以对外部依赖组件实现进行模拟并且替换掉, 从而使得单元测试将焦点只放在当前的单元功能。因为在为代码进行单元测试的同时,会发现该模块依赖于其他的模块,例如数据库,网络,或者第三方模块的存在,而我们对一个模块进行单元测试的目的,是测试当前模块正常工作,这样就要避开对其他模块的依赖,而mock主要作用便在于,专注于待测试的代码。而在单元测试中,如何灵活的使用mock模块是核心所在。
|
|
|
|
|
|
|
|
|
|
## mock模块的使用
|
|
|
|
|
在mock模块中,两个常用的类型为Mock,MagicMock,两个类的关系是MagicMock继承自Mock,最重要的两个属性是return_value, side_effect。
|
|
|
|
|
>>> from mock import Mock
|
|
|
|
|
>>> fake_obj = Mock()
|
|
|
|
|
>>>fake_obj.return_value = 'This is a mock object'
|
|
|
|
|
>>> fake_obj()
|
|
|
|
|
'This is a mock object'
|
|
|
|
|
我们通过Mock()可以创建一个mock对象,通过renturn_value 指定它的返回值。即当下文出现fake_obj()会返回其return_value所指定的值。
|
|
|
|
|
|
|
|
|
|
也可以通过side_effect指定它的副作用,这个副作用就是当你调用这个mock对象是会调用的函数,也可以选择抛出一个异常,来对程序的错误状态进行测试。
|
|
|
|
|
>>>def b():
|
|
|
|
|
... print 'This is b'
|
|
|
|
|
...
|
|
|
|
|
>>>fake_obj.side_effect = b
|
|
|
|
|
>>>fake_obj()
|
|
|
|
|
This is b
|
|
|
|
|
>>>fake_obj.side_effect = KeyError('This is b')
|
|
|
|
|
>>>fake_obj()
|
|
|
|
|
...
|
|
|
|
|
KeyError: 'This is b'
|
|
|
|
|
|
|
|
|
|
如果要模拟一个对象而不是函数,你可以直接在mock对象上添加属性和方法,并且每一个添加的属性都是一个mock对象,也就是说可以对这些属性进行配置,并且可以一直递归的定义下去。
|
|
|
|
|
>>>fake_obj.fake_a.return_value = 'This is fake_obj.fake_a'
|
|
|
|
|
>>>fake_obj.fake_a()
|
|
|
|
|
'This is fake_obj.fake_a'
|
|
|
|
|
上述代码片段中fake_obj是一个mock对象,而fake_obj.fake_a的这种形式使得fake_a变成了fake_obj的一个属性,作用是在fake_obj.fake_a()调用时会返回其return_value。
|
|
|
|
|
另外也可以通过为side_effect指定一个列表,这样在每次调用时会依次返回,如下:
|
|
|
|
|
>>> fake_obj = Mock(side_effect = [1, 2, 3])
|
|
|
|
|
>>>fake_obj()
|
|
|
|
|
1
|
|
|
|
|
>>>fake_obj()
|
|
|
|
|
2
|
|
|
|
|
>>>fake_obj()
|
|
|
|
|
3
|
|
|
|
|
|
|
|
|
|
## 函数如何mock
|
|
|
|
|
在rbd_api.py文件中如下内容:
|
|
|
|
|
import DAO_PoolMgr
|
|
|
|
|
|
|
|
|
|
def checkpoolstat(pool_name)
|
|
|
|
|
ret, poolstat = DAO_PoolMgr.DAO_query_ispoolok(pool_name)
|
|
|
|
|
if ret != MGR_COMMON.MONGO_SUCCESS:
|
|
|
|
|
return ret
|
|
|
|
|
if poolstat is False:
|
|
|
|
|
return MGR_COMMON.POOL_STAT_ERROR
|
|
|
|
|
return MGR_COMMON.SUCCESS
|
|
|
|
|
|
|
|
|
|
要为这个函数撰写单元测试,因为其有数据库的操作,因而就需要mock 出DAO_query_ispoolok操作。
|
|
|
|
|
因此,我们在test_rbd_api.py文件中可以这么写:因为DAO_query_ispoolok是类DAO_PoolMgr的操作,因此可以这么写:
|
|
|
|
|
#!/usr/bin/python
|
|
|
|
|
import DAO_PoolMgr
|
|
|
|
|
import unittest
|
|
|
|
|
import rbd_api as rbdAPI
|
|
|
|
|
|
|
|
|
|
class TestAuxiliaryFunction(unittest.TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
self.pool_name = "aaa"
|
|
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
|
self.pool_name = None
|
|
|
|
|
@mock.patch.object(DAO_PoolMgr, "DAO_query_ispoolok")
|
|
|
|
|
def test_checkpoolstat(self, mock_DAO_query_ispoolok):
|
|
|
|
|
mock_DAO_query_ispoolok.return_value = (MGR_COMMON.POOL_STAT_ERROR, None)
|
|
|
|
|
self.assert(rbdAPI.checkpoolstat(self.pool_name), MGR_COMMON.POOL_STAT_ERROR)
|
|
|
|
|
|
|
|
|
|
mock_DAO_query_ispoolok.return_value = (MGR_COMMON.SUCCESS, False)
|
|
|
|
|
self.assert(rbdAPI.checkpoolstat(self.pool_name), MGR_COMMON.POOL_STAT_ERROR)
|
|
|
|
|
|
|
|
|
|
mock_DAO_query_ispoolok.return_value = (MGR_COMMON.SUCCESS, True)
|
|
|
|
|
self.assert(rbdAPI.checkpoolstat(self.pool_name), MGR_COMMON.SUCCESS)
|
|
|
|
|
|
|
|
|
|
测试用例上的装饰器含义如下:
|
|
|
|
|
@mock.pathc.object(类名,“类中函数名”),而如果想要忽略某个测试用例,则可以通过装饰器@unittest.skip(“原因”)
|
|
|
|
|
而对于另外一种情形则是在另外一个函数中调用了checkpoolstat函数。
|
|
|
|
|
如下rbd_api.py:
|
|
|
|
|
def checkpoolstat():
|
|
|
|
|
……
|
|
|
|
|
|
|
|
|
|
class Disk(Resource):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
……
|
|
|
|
|
def delete(self, pool, img):
|
|
|
|
|
ret = rbd_api.checkpoolstat()
|
|
|
|
|
……
|
|
|
|
|
这样,我们在为delete函数撰写单元测试时,也可以在test_rbd_api.py中使用如下的方式:
|
|
|
|
|
import rbd_api
|
|
|
|
|
|
|
|
|
|
class TestDisk(unittest.TestCase):
|
|
|
|
|
def setup():
|
|
|
|
|
…
|
|
|
|
|
def teardown():
|
|
|
|
|
…
|
|
|
|
|
@mock.patch(“rbd_api.checkpoolstat”, Mock(return_value = True))
|
|
|
|
|
def test_delete():
|
|
|
|
|
# rbd_api.checkpoolstat 已经成为一个mock对象了,调用时返回True
|
|
|
|
|
…
|
|
|
|
|
此时的装饰器应该为:
|
|
|
|
|
@mock.patch(“模块名.函数名”)
|
|
|
|
|