Mock相关知识和简单应用

一.moco的简单应用

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'

我们通过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

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

要为这个函数撰写单元测试,因为其有数据库的操作,因而就需要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(“模块名.函数名”)

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)

3.5 with子句mock

#!/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

由于是在with子句中要进行mock,在此简单的对with的知识点进行说明: 要使用 with 语句,首先要明白上下文管理器这一概念。有了上下文管理器,with 语句才能工作。 下面是一组与上下文管理器和with 语句有关的概念。

  • 上下文管理协议(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)
            

    3.6 连续mock

    在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))
原文地址:https://www.cnblogs.com/shen-qiang/p/12002168.html