master
p73cyqnj8 4 years ago
parent 8a334211d8
commit 7aab77efe1

@ -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模块中两个常用的类型为MockMagicMock两个类的关系是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(“模块名.函数名”)

Loading…
Cancel
Save