首页 > 技术文章 > mock服务从入门到实践

dydxw 2020-02-27 13:37 原文

1.如何在接口开发阶段编写测试脚本

利用fiddle的autoresponeder或以代码(mock)实现模拟接口返回数据

2.mock服务介绍以及实现原理

mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。

对象

这个虚拟的对象就是mock对象。mock对象就是真实对象在调试期间的代替品。Mock对象是mock模块中最重要的概念。Mock对象就是mock模块中的一个类的实例,这个类的实例可以用来替换其他的Python对象,来达到模拟的效果。Mock类的定义如下:
class Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, **kwargs)

对象使用范畴

真实对象具有不可确定的行为,产生不可预测的效果,(如:股票行情,天气预报)真实对象很难被创建的 真实对象的某些行为很难被触发真实对象实际上还不存在的(和其他开发小组或者和新的硬件打交道)等等。

Mock对象的一般用法是这样的:

  1. 找到你要替换的对象,这个对象可以是一个类,或者是一个函数,或者是一个类实例。

  2. 然后实例化Mock类得到一个mock对象,并且设置这个mock对象的行为,比如被调用的时候返回什么值,被访问成员的时候返回什么值等。

  3. 使用这个mock对象替换掉我们想替换的对象,也就是步骤1中确定的对象。

  4. 之后就可以开始写测试代码,这个时候我们可以保证我们替换掉的对象在测试用例执行的过程中行为和我们预设的一样。

举个例子来说:我们有一个简单的客户端实现,用来访问一个URL,当访问正常时,需要返回状态码200,不正常时,需要返回状态码404。首先,我们的客户端代码实现如下:

# 文件名:client
import requests


def send_requestr(url):
    r = requests.get(url)
    return r.status_code

def visit_ustack():
    return send_requestr("http://www.ustack.com")

外部模块调用visit_ustack()来访问UnitedStack的官网。下面我们使用mock对象在单元测试中分别测试访问正常和访问不正常的情况。

import unittest
from unittest import mock
from . import client


class TestClient(unittest.TestCase):

    def test_success_request(self):
        success_send = mock.Mock(return_value='200')
        client.send_request = success_send
        self.assertEqual(client.visit_ustack(), '200')

    def test_fail_request(self):
        fail_send = mock.Mock(return_value='404')
        client.send_request = fail_send
        self.assertEqual(client.visit_ustack(), '404')

关键步骤

  1. 找到要替换的对象:我们需要测试的是visit_ustack这个函数,那么我们需要替换掉send_request这个函数。

  2. 实例化Mock类得到一个mock对象,并且设置这个mock对象的行为。在成功测试中,我们设置mock对象的返回值为字符串“200”,在失败测试中,我们设置mock对象的返回值为字符串"404"。

  3. 使用这个mock对象替换掉我们想替换的对象。我们替换掉了client.send_request

  4. 写测试代码。我们调用client.visit_ustack(),并且期望它的返回值和我们预设的一样。

上面这个就是使用mock对象的基本步骤了。在上面的例子中我们替换了自己写的模块的对象,其实也可以替换标准库和第三方模块的对象,方法是一样的:先import进来,然后替换掉指定的对象就可以了。

使用一个接口来描述这个对象。在产品代码中实现这个接口,在测试代码中实现这个接口,在被测试代码中只是通过接口来引用对象,所以它不知道这个引用的对象是真实对象,还是mock对象。

对象实例

一个闹钟根据时间来进行提醒服务,如果过了下午5点钟就播放音频文件提醒大家下班了,如果我们要利用真实的对象来测试的话就只能苦苦等到下午五点,然后把耳朵放在音箱旁,我们应该利用mock对象来进行测试,这样我们就可以模拟控制时间了,而不用苦苦等待时钟转到下午5点钟了。

mock的重要性

mock就是对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建的方法。项目开发和测试过程中,遇到以下的情况时,就需要模拟结果返回。
1.当另一方接口或服务还未完成,阻碍项目进度时,可以通过mock的方式,实现并行开发。
2.另一方接口不稳定,而这边需要一个稳定的结果才能继续往下走流程时,也可以使用mock。有时候自动化测试需要一个持续稳定的环境,也可以对不是很重要的服务进行mock处理。
3.需要模拟异常情况,但是这种异常不容易触发时,可以使用mock实现。

3.安装mock

pip  install mock

4.在case中通过底层函数实现mock

import unittest
import json
from demo import RunMain   #导入方法
from mock import mock   #导入mock
import HTMLTestRunner
class TestMethod(unittest.TestCase):
    def setUp(self):
      self.run = RunMain()   #每次都实例化
    def test_01(self):
      url = 'http://10.1.30.118:3200/provider/user/user/login?loginName=15519560190&loginModel=0&password=12345678&loginType=01'
      data = {
    'loginName':'15519560190',
    'loginModel':'01',
    'password':'12345678',
    'loginModel':'0',
    'timeStamp':'1582616038764'
    }
      mock_data = mock.Mock(return_value=data) #把data作为mock模拟的数据
      self.run.run_main = mock_data                #把模拟的数据传给run_main
      res = self.run.run_main(url,'POST',data)   #返回的res就是data
      print(res)
      print(type(res))           #返回的数据是字典类型,这时候就不能用之前的json.loads()方法把返回结果转为字典了
      self.assertEqual(res['loginModel'],'0')
      print('第一个case')

运行结果

 

 5.重构封装mock服务

# -*- coding: utf-8 -*-
#mock_demp.py

from mock import mock
#重构mock  封装
def mock_test(mock_method,request_data,url,method,respone_data):    #参数就是mock要模拟的方法,请求数据,url,方法,返回数据
    mock_method = mock.Mock(return_value=respone_data)    #模拟的是一个方法,把返回数据作为mock数据
    res = mock_method(url,method,request_data)        #方法的参数,执行res
    print(res)
    return res
# -*- coding: utf-8 -*-
import unittest
import json
from demo import RunMain   #导入方法
from mock import mock
from mock_demo import mock_test  #导入mock_test
import HTMLTestRunner
class TestMethod(unittest.TestCase):
    def setUp(self):
      self.run = RunMain()   #每次都实例化
    def test_01(self):
      url = 'http://10.1.30.118:3200/provider/user/user/login?loginName=15519560010&loginModel=0&password=12345678&loginType=01'
      data = {
    'loginName':'15519560010',
    'loginModel':'01',
    'password':'12345678',
    'loginModel':'0',
    'timeStamp':'1582616038764'
    }
      #mock_data = mock.Mock(return_value=data)
      #self.run.run_main = mock_data
      #res = self.run.run_main(url,'POST',data)
      mock_test(self.run.run_main,data,url,'POST',data) #调用mock——test方法(模拟的方法,请求数据,url,方法,响应数据)
      #print(res)
      #print(type(res))
      #self.assertEqual(res['loginModel'],'0')
      print('第一个case')
      

 

执行结果

 

 

扩展

1、class Mock的参数

先来看看Mock这个类的参数,在上面看到的类定义中,我们知道它有好几个参数,这里介绍最主要的几个:

  • name: 这个是用来命名一个mock对象,只是起到标识作用,当你print一个mock对象的时候,可以看到它的name。
  • return_value: 这个我们刚才使用过了,这个字段可以指定一个值(或者对象),当mock对象被调用时,如果side_effect函数返回的是DEFAULT,则对mock对象的调用会返回return_value指定的值。
  • side_effect: 这个参数指向一个可调用对象,一般就是函数。当mock对象被调用时,如果该函数返回值不是DEFAULT时,那么以该函数的返回值作为mock对象调用的返回值

2、mock对象的自动创建

当访问一个mock对象中不存在的属性时,mock会自动建立一个子mock对象,并且把正在访问的属性指向它,这个功能对于实现多级属性的mock很方便。

client = mock.Mock()
client.v2_client.get.return_value = '200'

这个时候,你就得到了一个mock过的client实例,调用该实例的v2_client.get()方法会得到的返回值是"200"。

从上面的例子中还可以看到,指定mock对象的return_value还可以使用属性赋值的方法。

3、对方法调用进行检查

mock对象有一些方法可以用来检查该对象是否被调用过、被调用时的参数如何、被调用了几次等。实现这些功能可以调用mock对象的方法,具体的可以查看mock的文档。这里我们举个例子。

还是使用上面的代码,这次我们要检查visit_ustack()函数调用send_request()函数时,传递的参数类型是否正确。我们可以像下面这样使用mock对象。

class TestClient(unittest.TestCase):

    def test_call_send_request_with_right_arguments(self):
        client.send_request = mock.Mock()
        client.visit_ustack()
        self.assertEqual(client.send_request.called, True)
        call_args = client.send_request.call_args
        self.assertIsInstance(call_args[0][0], str)

Mock对象的called属性表示该mock对象是否被调用过。

Mock对象的call_args表示该mock对象被调用的tuple,tuple的每个成员都是一个mock.call对象。mock.call这个对象代表了一次对mock对象的调用,其内容是一个tuple,含有两个元素,第一个元素是调用mock对象时的位置参数(*args),第二个元素是调用mock对象时的关键字参数(**kwargs)。

现在来分析下上面的用例,我们要检查的项目有两个:

  1. visit_ustack()调用了send_request()

  2. 调用的参数是一个字符串

4、patch和patch.object

在了解了mock对象之后,我们来看两个方便测试的函数:patchpatch.object。这两个函数都会返回一个mock内部的类实例,这个类是class _patch。返回的这个类实例既可以作为函数的装饰器,也可以作为类的装饰器,也可以作为上下文管理器。使用patch或者patch.object的目的是为了控制mock的范围,意思就是在一个函数范围内,或者一个类的范围内,或者with语句的范围内mock掉一个对象。我们看个代码例子即可:
class TestClient(unittest.TestCase):

    def test_success_request(self):
        status_code = '200'
        success_send = mock.Mock(return_value=status_code)
        with mock.patch('client.send_request', success_send):
            from client import visit_ustack
            self.assertEqual(visit_ustack(), status_code)

    def test_fail_request(self):
        status_code = '404'
        fail_send = mock.Mock(return_value=status_code)
        with mock.patch('client.send_request', fail_send):
            from client import visit_ustack
            self.assertEqual(visit_ustack(), status_code)

这个测试类和我们刚才写的第一个测试类一样,包含两个测试,只不过这次不是显示创建一个mock对象并且进行替换,而是使用了patch函数(作为上下文管理器使用)。

patch.objectpatch的效果是一样的,只不过用法有点不同。举例来说,同样是上面这个例子,换成patch.object的话是这样的:

    def test_fail_request(self):
        status_code = '404'
        fail_send = mock.Mock(return_value=status_code)
        with mock.patch.object(client, 'send_request', fail_send):
            from client import visit_ustack
            self.assertEqual(visit_ustack(), status_code)

就是替换掉一个对象的指定名称的属性,用法和setattr类似。

 

 

推荐阅读