zoukankan      html  css  js  c++  java
  • 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))
  • 相关阅读:
    harbor install & docker-compose
    ngx安装 (转)
    docker对镜像自动重启的设置
    docker安装脚本
    sql中的递归拼接
    【HC89S003F4开发板】 4端口消抖
    【HC89S003F4开发板】 6crc校验
    【HC89S003F4开发板】 3串口调试
    【HC89S003F4开发板】 1环境搭建
    用mkdocs在gitee码云上建立一个简单的文档博客
  • 原文地址:https://www.cnblogs.com/shen-qiang/p/12002168.html
Copyright © 2011-2022 走看看