Mock相关知识和简单应用
moco地址:https://github.com/dreamhead/moco
api文档地址: https://github.com/dreamhead/moco/blob/master/moco-doc/apis.md
启动命令: java -jar moco-runner-1.0.0-standalone.jar http -p 12306 -c foo.json
moco只关注服务器的配置,也就是客户端与服务端,或者更加具体的说就是请求和响应。
实例:
二.mock的介绍与详解
mock在python3.3之前是第三方库,在python3.3版本之后是标准库,只需要导入就可以使用。
Python3.3版本之前引入方式是
import mock
python3.3版本之后的引入方式是
from unittest import mock
Mock的意义:Mock能够让我们模拟那些在单元测试中不可用或太笨重的资源
Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象,以达到摸你对象的行为。Mock它可以替换Python对象。
1.使用现实情况:
1.环境由于客观原因导致无法搭建
2.搭建服务器需要大量的工作才可以
案例:
1.测试一个网站
不存在的网站www.wuya.com
200
404
500
400
401
403
模拟进行200,404,
2.测试C盘
2.Mock和MagicMock
在单元测试进行的同时,就离不开mock模块的存在,初次接触这个概念的时候会有这样的疑问:把要测的东西都模拟掉了还测试什么呢? 但在,实际生产中的项目是非常复杂的,对其进行单元测试的时候,会遇到以下问题: •接口的依赖 •外部接口调用 •测试环境非常复杂 单元测试应该只针对当前单元进行测试, 所有的内部或外部的依赖应该是稳定的, 已经在别处进行测试过的.使用mock 就可以对外部依赖组件实现进行模拟并且替换掉, 从而使得单元测试将焦点只放在当前的单元功能。
因为在为代码进行单元测试的同时,会发现该模块依赖于其他的模块,例如数据库,网络,或者第三方模块的存在,而我们对一个模块进行单元测试的目的,是测试当前模块正常工作,这样就要避开对其他模块的依赖,而mock主要作用便在于,专注于待测试的代码。而在但与测试中,如何灵活的使用mock模块是核心所在。下面便以mock为核心,结合最近所写的代码,阐述mock模块的使用
3. 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'
>>>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(side_effect = [1, 2, 3]) >>>fake_obj() 1 >>>fake_obj() 2 >>>fake_obj() 3
3.1 函数的如何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
#!/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)
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(“模块名.函数名”)
3.2 链式函数抛出异常
在rbd_api.py文件中,有一行代码如下:
rbdServ.OpRBD = MagicMock()
rbdServ.OpRBD(pool).side_effect = rados.Error(“Error: error connecting to the cluster: error code 24”)
3.3 全局函数如何mock
例如在文件rbd_api.py中有全局函数checkpoolstat(pool),它是一个全局函数,这样在进行单元测试的过程中,我们可能需要mock该函数。该函数的具体代码如下:
因此,我们在test_rbd_api.py文件中为该函数撰写单元测试,可以这么做。 在文件开始处导入该rbd_api模块。
import rbd_api as rbdAPI def test_patchInvalid_Parameter(self): …… rbdAPI.checkpoolstat.return_value = MGR_COMMON.POOL_STAT_ERROR 即可。
3.4 链式调用正常
在rbd_api文件中有如下代码行
ret = OpRBD(pool).flatten(img)
在第一个函数未出现异常,在flatten函数中返回值可以在test_rbd_api.py文件中如下写代码:
rbdServ.OpRBD(pool).snap_rollback = MagicMock(return_value = RBD_COMMON.CODE_EXEC_SUCCESS_MODIFY)
#!/usr/bin/python import rados class OpRBD: def __init__(self): ... def __del__(self): ... def resize(self, img, size): try: with rbd.Image(self.ioctx, img) as image: if image.size() < size: image.resize(size) else: return RBD_COMMON.CODE_ARGUMENT_LESS_THAN_ORIGINAL except rbd.ImageNotFound as exce1 print(exce1) return RBD_COMMON.CODE_IMAGE_NOT_FOUND
-
上下文管理协议(Context Management Protocol):包含方法 enter() 和 exit(),支持 该协议的对象要实现这两个方法。
-
上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了 enter() 和 exit() 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文, 负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。
-
运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 enter() 和exit() 方法实现,enter() 方法在语句体执行之前进入运行时上下文,exit() 在语句体执行完后从运行时上下文退出。with 语句支持运行时上下文这一概念。
-
上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象。
-
语句体(with-body):with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的 enter() 方法,执行完语句体之后会执行 exit() 方法。 出现异常时,如果 exit(type, value, traceback) 返回 False,则会重新抛出异常,让with 之外的语句逻辑来处理异常,这也是通用做法;如果返回 True,则忽略异常,不再对异常进行处理。因此,在对with子句进行mock时,要具有两个函数,exit, enter,并且如果在with语句体重抛出异常并被with之外的代码进行捕获异常,要使得exit返回False,因此可以撰写测试代码如下
#!/usr/bin/python import rados class OpRBD: def __init__(self): ... def __del__(self): ... def resize(self, img, size): try: with rbd.Image(self.ioctx, img) as image: if image.size() < size: image.resize(size) else: return RBD_COMMON.CODE_ARGUMENT_LESS_THAN_ORIGINAL except rbd.ImageNotFound as exce1 print(exce1) return RBD_COMMON.CODE_IMAGE_NOT_FOUND class TestOpRBD(unittest.TestCase): def setUp(self): ... def tearDown(self): ... def test_resize(self): fake_image = Mock() fake_image.__enter__ = Mock(return_value = fake_image) fake_image.__exit__ = Mock(return_value = True) rbd.Image = Mock(return_value = fake_image) size = 1073741824L / 2 fake_image.size = Mock(return_value = 1073741824L) fake_image.resize = Mock(return_value = None) self.assertEqual(self.opRBD.resize(self.img, size), RBD_COMMON.CODE_ARGUMENT_LESS_THAN_ORIGINAL) size = 2 * 1073741824L self.assertEqual(self.opRBD.resize(self.img, size), RBD_COMMON.CODE_EXEC_SUCCESS_MODIFY) rbd.Image = Mock(side_effect = rbd.ImageNotFound("%s image not found!" %self.img)) self.assertEqual(self.resize(self.img, size), RBD_COMMON.CODE_IMAGE_NOT_FOUND)
在rbd_api文件中有一个OpRados类的内容如下:
#!/usr/bin/python import rados class OpRados: def __init__(self): self.cluster = rados.Rados(conffile=rconf['conffile']) self.cluster.connect() def __del__(self): self.cluster.shutdown() def lists(self): return util.return_format(RBD_COMMON.CODE_EXEC_SUCCESS_GET, "", self.cluster.list_pools())
为该类写单元测试,具体代码如下:
#!/usr/bin/python import rados import unittest from mock import Mock class TestOpRados(unittest.TestCase): def setUp(self): fake_Rados = Mock() fake_Rados.connect = Mock(return_value = None) fake_Rados.shutdown = Mock(return_value = None) fake_Rados.list_pools = Mock(return_value = ["sqh", "sqh1"]) # 注意:此处要使得rados.Rados()调用返回fake_Rados. # 如果写成rados.Rados = fake_Rados,只能使得self.cluster重新生成一个Mock对象 # 无法有效的控制为fake_Rados所添加的属性。 rados.Rados = Mock(return_value = fake_Rados) self.opRados = OpRados() def tearDown(self): fake_Rados = None self.opRados = None def test_list(self): return_list = ["sqh", "sqh1"] self.assertEqual(self.opRados.lists(), util.return_format(RBD_COMMON.CODE_EXEC_SUCCESS_GET, "", return_list))