前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python接口自动化 之 Mock服务的使用

python接口自动化 之 Mock服务的使用

作者头像
软件测试君
发布2023-10-09 17:05:41
2800
发布2023-10-09 17:05:41
举报
文章被收录于专栏:测试人生测试人生
Mock实现原理和实现机制

在某些时候,后端在开发接口的时候,处理逻辑非常复杂,在测试的时候,后端在未完成接口的情况下该如何去测试呢?

1、什么是mock

Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西。准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉指定的Python对象,以达到模拟对象的行为。

2、为什么要使用mock

之所以使用mock测试,是因为真实场景很难实现或者短期实现起来很困难。主要场景有:

  • 真实对象可能还不存在(接口还没有完成开发)
  • 真实对象很难搭建起来(第三方支付联调)
  • 真实对象的行为很难触发(例如网络错误)
  • 真实对象速度很慢(例如一个完整的数据库,在测试之前可能需要初始化)
  • 真实对象可能包含不能用作测试(而不是为实际工作)的信息和方法
  • 真实的对象是用户界面,或包括用户页面在内
  • 真实的对象使用了回调机制
  • 真实对象的行为是不确定的(例如当前的时间或当前的温度)
3、Mock对象适用场景

需要将当前被测单元和其依赖模块独立开来,构造一个独立的测试环境,不关注被测单元的依赖对象,只关注被测单元的功能逻辑。比如被测代码中需要依赖第三方接口返回值进行逻辑处理,可能因为网络或者其他环境因素,调用第三方经常会中断或者失败,无法对被测单元进行测试,这个时候就可以使用mock技术来将被测单元和依赖模块独立开来,使得测试可以进行下去。 场景如下:

  • 被测单元依赖的模块尚未开发完成,而被测单元需要依赖模块的返回值进行后续处理。
  • 前后端项目中,后端接口开发完成之前,接口联调;
  • 依赖的上游项目的接口尚未开发完成,需要接口联调测试;比如service层的代码中,包含对Dao层的调用,但是,DAO层代码尚未实现
  • 被测单元依赖的对象较难模拟或者构造比较复杂。比如,支付宝支付的异常条件有很多,但是模拟这种异常条件很复杂或者无法模拟,比如,查询聚划算的订单结果,无法在测试环境进行模拟。
4、Mock测试的优势

「团队可以并行工作」有了Mock,前后端人员只需要定义好接口文档就可以开始并行工作,互不影响,只在最后的联调阶段往来密切;后端与后端之间如果有接口耦合,也同样能被Mock解决;测试过程中如果遇到依赖接口没有准备好,同样可以借助Mock;不会出现一个团队等待另一个团队的情况。这样的话,开发自测阶段就可以及早开展,从而发现缺陷的时机也提前了,有利于整个产品质量以及进度的保证。

「开启TDD模式,即测试驱动开发」单元测试是TDD实现的基石,而TDD经常会碰到协同模块尚未开发完成的情况,但是有了mock,这些一切都不是问题。当接口定义好后,测试人员就可以创建一个Mock,把接口添加到自动化测试环境,提前创建测试。

「可以模拟那些无法访问的资源」比如说,你需要调用一个“墙”外的资源来方便自己调试,就可以自己Mock一个。

「隔离系统」假如我们需要调用一个post请求,为了获得某个响应,来看当前系统是否能正确处理返回的“响应”,但是这个post请求会造成数据库中数据的污染,那么就可以充分利用Mock,构造一个虚拟的post请求,我们给他指定返回就好了。

「可以用来演示」假如我们需要创建一个演示程序,并且做了简单的UI,那么在完全没有开发后端服务的情况下,也可以进行演示。说到演示了,假如你已经做好了一个系统,并且需要给客户进行演示,但是里面有些真实数据并不想让用户看到,那么同样,你可以用Mock接口把这些敏感信息接口全部替换。

「测试覆盖度」假如有一个接口,有100个不同类型的返回,我们需要测试它在不同返回下,系统是否能够正常响应,但是有些返回在正常情况下基本不会发生,比如,我们需要测试在当接口发生500错误的时候,app是否崩溃,别告诉我你一定要给服务端代码做些手脚让他返回500 。而使用mock,这一切就都好办了,想要什么返回就模拟什么返回,不用再担心我的测试覆盖度了!

5、Mock测试存在的问题

使用Mock测试有时可以提高团队的开发效率,但当B、C都开发完成代码后,这时应该把E2E测试代码从使用Mock测试改为调用真实的模块,以避免出现模块之间集成部分漏测的问题。这里说mock存在的问题,主要是让开发和测试不要过分的依赖/相信mock接口。

使用mock时,切记的几点:

1)测试人员不应该被覆盖率高的E2E自动化测试所迷惑,覆盖率高不代表没有问题。尤其在接手新项目中,需要查看E2E测试中有没有使用Mock测试,进一步去判断这些地方使用Mock测试是否合理,这些Mock测试是否应该换成真实模块间的调用和集成。

2)当把mock接口换成实际接口后,测试/开发也必须把之前的测试重新做一遍。

ps: 当你使用mock接口来提高效率,请注意:你的工作量其实是比 直接只用实际接口 多了 一倍的。如果测试时,偷懒,替换成实际接口后,只是简单测试,那么 当实际接口和mock预期接口有差异时,故障便和你相遇了。

建议: mock接口只能主流程联调/ 异常返回测试,不要过分依赖mock接口进行测试。

3)测试完毕,上线前,请一定确保 为了mock而做的相关代码/配置文件的修改,已经完全恢复了。

建议:上线checklist中条条列出,并上线前review

Mock的使用

1、如何使用mock

「思路:」

  • 通过代码制造假的输出(结果)
  • 通过代码去模拟假的接口返回数据(模拟的是:「访问真实接口的过程」就可以省略)
2、Mock的安装和导入

在Python 3.3以前的版本中,需要另外安装mock模块,可以使用pip命令来安装:

代码语言:javascript
复制
pip install mock

然后在代码中就可以直接import进来:

代码语言:javascript
复制
import mock

从Python 3.3开始,mock模块已经被合并到标准库中,被命名为「unittest.mock」,可以直接import进来使用:

代码语言:javascript
复制
from unittest import mock

「mock的本质:」

  • 就算接口未开发完,依据约定好的格式要求,进行数据和对象的模拟 摆脱环境问题,如测试服务器可能很不好搭建,或者搭建效率很低。

「举个栗子:」基本代码

代码语言:javascript
复制
import requests

url = 'http://localhost:8090/login'
data = {
    "username":"xiaoqiang",
    "password":"1"
}
res=requests.post(url,data).json()
print(res)

接口的呈现角度看应该是个方法,更合理,那么就有了如下:

代码语言:javascript
复制
import requests

url = 'http://localhost:8090/login'
data = {
    "username": "xiaoqiang",
    "password": "1"
}

def requests_post(url, data):
    res = requests.post(url, data).json()
    print(res)
    return res

其实本质来看,就是要模拟的是「返回值」

3、使用mock进行数据模拟

示例代码

代码语言:javascript
复制
# -*- coding: utf-8 -*-
"""
# @Time    : 2023/08/07 14:47
# @Author  : longrong.lang
# @FileName: mock_demo.py
# @Software: PyCharm
# @Blog    :https://www.cnblogs.com/longronglang/
# @Motto:I am a slow walker , but I never walk backwards.
"""
import unittest

import mock
import requests


def post_request(url, data):
    """
    POST请求
    """
    res = requests.post(url, data).json()
    print(res)
    return res

def get_request(url):
    """
    get请求,返回code码
    """
    res = requests.get(url).status_code
    print(res)
    return res

class TestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        super().setUpClass()

    def test_case01(self):
        """
        mock测试案例1
        """
        url = 'http://localhost:8090/login'
        # 跟开发约定好的返回示例
        data = {
            "username": "xiaoqiang",
        }
        # 模拟返回的值
        mock_test = mock.Mock(return_value=data)
        # 执行结果的值
        post_request = mock_test
        # 本质我们想模拟的就是他呀!
        # res=post_request(url, data)
        # 这么写将会被执行,要去掉方法
        # res = post_request(url, data)
        # 正确写法
        res = post_request
        self.assertEqual("111", res())

    def test_case02(self):
        """
        mock测试案例2
        """
        # 模拟返回的值
        mock_test =mock.Mock(return_value='200')
        # 执行结果的值
        get_request = mock_test
        res=get_request
        self.assertEqual('200',res())


    @classmethod
    def tearDownClass(cls) -> None:
        super().tearDownClass()


if __name__ == '__main__':
    unittest.main()

mockrunner的使用

1、下载安装

https://share.weiyun.com/AQT9d8Us

2、启动服务

java -jar moco-runner-0.12.0-standalone.jar http -p 8008 -c config.json

「效果」

3、配置config.json文件

写好配置config.json文件,就可以进行mock数据啦,示例如下:

代码语言:javascript
复制
[
  {
    "response": {
      "text": "This is moco demo!"
    }
  },
  {
    "description": "带参数的get请求示例",
    "request": {
      "uri": "/getwithparam",
      "method": "get",
      "queries": {
        "name": "ztt",
        "age": "18"
      }
    },
    "response": {
      "text": "ztt come back"
    }
  },
  {
    "description": "带参数的post请求示例",
    "request": {
      "uri": "/postwithparam",
      "forms": {
        "name": "ztt",
        "age": "18"
      }
    },
    "response": {
      "json":{
        "name": "ztt",
        "message": "ztt is coming"
      }
    }
  }
]

访问示例;http://localhost:8008/getwithparam?name=ztt&age=18

注意:保存即是热部署

四、mock在单元测试中的实际使用

1、requests的封装

「示例代码」

代码语言:javascript
复制
# -*- coding: utf-8 -*-
"""
# @Time    : 2023/08/08 15:35
# @Author  : longrong.lang
# @FileName: base_request.py
# @Software: PyCharm
# @Blog    :https://www.cnblogs.com/longronglang/
# @Motto:I am a slow walker , but I never walk backwards.
"""
import json
import sys

import requests
import os

base_path = os.getcwd()
base_path = os.path.dirname(base_path)
sys.path.append(base_path)
host = 'http://localhost:8090/'


class BaseRequest:
    @staticmethod
    def send_post(url, data, **kwargs):
        """
        发送POST请求
        @param url: 请求路径
        @param data: 请求参数
        @param kwargs: 多参
        @return: 响应信息
        """
        res = requests.post(host + url, headers=data, **kwargs).text
        return res

    @staticmethod
    def send_get(url, data, **kwargs):
        """
        发送GET请求
        @param url: 请求路径
        @param data: 请求参数
        @param kwargs: 多参
        @return: 响应信息
        """
        res = requests.get(host + url, params=data, **kwargs).text
        return res

    @staticmethod
    def send_put(url, data, **kwargs):
        """
        发送PUT请求
        @param url: 请求路径
        @param data: 请求参数
        @param kwargs: 多参
        @return: 响应信息
        """
        res = requests.put(host + url, params=data, **kwargs).text
        return res

    @staticmethod
    def send_delete(url, data, **kwargs):
        """
        发送DELETE请求
        @param url: 请求路径
        @param data: 请求参数
        @param kwargs: 多参
        @return: 响应信息
        """
        res = requests.delete(host + url, params=data, **kwargs).text
        return res

    @staticmethod
    def read_json():
        """
        读取json文件
        @return: json文件内容
        """
        with open(base_path + "\\config\\user.json", encoding='utf-8') as f:
            data = json.load(f)
        return data

    @staticmethod
    def get_jsonvalue(caseName, paramName):
        """
        获取配置文件中入参值
        @param caseName: 用例方法
        @param paramName: 用例请求参数名字
        @return: 请求参数值
        """
        json_str = request.read_json()
        request_param = json_str[caseName]
        request_param[paramName]

    def run_main(self, method, url, data, **kwargs):
        """
        执行方法传url
        @param method: 请求方法
        @param url: 请求路径
        @param data:请求参数
        @param kwargs: 多参
        @return: 响应信息
        """

        if method == "get":
            res = self.send_get(url, data, **kwargs)
        elif method == "post":
            res = self.send_post(url, data, **kwargs)
        elif method == "put":
            res = self.send_put(url, data, **kwargs)
        else:
            res = self.send_delete(url, data, **kwargs)
        try:
            res = json.loads(res)
        except:
            print("不是json,只是")
        return res


request = BaseRequest()

if __name__ == '__main__':
    json_str = request.read_json()
    json_str = json_str['test_getStudents']
    print(json_str['method'])
2、结合封装mock的实际应用

「示例代码」

代码语言:javascript
复制
# -*- coding: utf-8 -*-
"""
# @Time    : 2023/08/08 16:08
# @Author  : longrong.lang
# @FileName: test_case.py
# @Software: PyCharm
# @Blog    :https://www.cnblogs.com/longronglang/
# @Motto:I am a slow walker , but I never walk backwards.
"""
import unittest

import mock

from base.base_request import request

config = request.read_json()


class TestCase(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        super().setUpClass()

    def test_getStudents(self):
        """
        测试get请求获取学生接口
        """
        param = config['test_getStudents']
        method = param['method']
        url = param['url']
        data = param['data']
        # 模拟的请求方法
        mock_method = mock.Mock(return_value=data)
        request.run_main = mock_method
        res = request.run_main(method, url, None)
        print(res)
        self.assertEqual(res['msg'], 'success')

    def test_studentAdd(self):
        """
        入参为json,带请求头的post请求
        :return:
        """
        param = config['test_studentAdd']
        method = param['method']
        url = param['url']
        headers = param['headers']
        request_body = param['request_body']
        data = param['data']
        # 模拟的请求方法
        request.run_main = mock.Mock(return_value=data)
        res = request.run_main(method, url, headers, json=request_body)
        print(res)
        self.assertEqual(res['msg'], 'success')

    def test_upload(self):
        """
        上传文件接口
        :return:
        """
        param = config['test_upload']
        method = param['method']
        url = param['url']
        data = param['data']
        file = open('D:\\data.xls', 'rb')
        files = {'file': file}
        # 模拟方法调用
        request.run_main = mock.Mock(return_value=data)
        res = request.run_main(method, url, None, files=files)
        file.close()
        print(res)
        self.assertEqual(res['msg'], '上传文件成功!')

    def test_studentUpdate(self):
        """
        put请求示例
        :return:
        """
        param = config['test_studentUpdate']
        method = param['method']
        url = param['url']
        params = param['params']
        data = param['data']
        # 模拟方法调用
        request.run_main = mock.Mock(return_value=data)
        res = request.run_main(method, url, params)
        print(res)
        self.assertEqual(res['msg'], 'success')

    def test_studentDelete(self):
        """
        删除接口
        :return:
        """
        param = config['test_studentDelete']
        method = param['method']
        url = param['url']
        data = param['data']
        # 模拟方法调用
        request.run_main = mock.Mock(return_value=data)
        res = request.run_main(method, url, None)
        print(res)
        self.assertEqual(res['msg'], 'success')

    @classmethod
    def tearDownClass(cls) -> None:
        super().tearDownClass()
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 软件测试君 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、什么是mock
  • 2、为什么要使用mock
  • 3、Mock对象适用场景
  • 4、Mock测试的优势
  • 5、Mock测试存在的问题
  • Mock的使用
    • 1、如何使用mock
      • 2、Mock的安装和导入
        • 3、使用mock进行数据模拟
        • mockrunner的使用
          • 1、下载安装
            • 2、启动服务
              • 3、配置config.json文件
              • 四、mock在单元测试中的实际使用
                • 1、requests的封装
                  • 2、结合封装mock的实际应用
                  相关产品与服务
                  测试服务
                  测试服务 WeTest 包括标准兼容测试、专家兼容测试、手游安全测试、远程调试等多款产品,服务于海量腾讯精品游戏,涵盖兼容测试、压力测试、性能测试、安全测试、远程调试等多个方向,立体化安全防护体系,保卫您的信息安全。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档