mock概念

mock 就是模拟接口返回的一系列数据,用自定义的数据替换接口实际需要返回的数据,通过自定义的数据来实现对下级接口模块的测试。
这里分为两类测试:一类是前端对接口的mock,一类是后端单元测试中涉及的mock

mock服务的产生

在软件测试中经常会出现一些特殊的接口,如银行支付结果获取接口,这个接口不可能实际去支付,那么就需要一个服务来承担这个接口的任务,所谓服务就是针对大多数人而不是单纯的针对自己,同时是针对大多数这种模拟操作,而不单单只是接口,也可以模拟服务,这个时候单独的mock已经不是那么适用了。

如何搭建一个自定义的mock服务

mock服务需要承载用户的一些操作行为,这种行为包括查询,查看,修改,删除,新增。在我们实际开发的过程中应该考虑到前端的一些简洁和灵活性,设计前端基本遵循以下原则:
1、界面简洁,内容平易近人
2、功能齐全,操作流程简单
3、反应速度快,性能优秀
在django中设计前端时,本项目采用的是前后端分离的思想,将前端静态页面直接返回,后端数据接口返回的方式,实现分离思想。

 return render(request, 'page/mock/edit-mock-data.html', {"mock_data_id": mock_data_id})
 return render(request, "page/mock/mock-config.html")

mock服务中所有的操作数据都存放在数据库中,django的models类创建代码如下

class MockModel(models.Model):
    """
    mock model
    """
    relate_interface = models.CharField(max_length=255, unique=True,  default='', verbose_name='关联接口')
    mock_data_id = models.IntegerField(default=-1, verbose_name="对应mock数据id")
    service = models.CharField(max_length=255, default="", verbose_name="服务方")
    origin_url = models.CharField(max_length=500, default="", verbose_name="正常url地址")
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    remark = models.CharField(max_length=500, verbose_name="备注")
    status = models.CharField(max_length=10, default='notDefault', verbose_name="是否默认状态")
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")     class Meta:
        db_table = 't_mock'
        verbose_name ='mock数据表'
        verbose_name_plural = verbose_name class MockDataModel(models.Model):
    """
    mock data model
    """
    # id = models.IntegerField(auto_created=True,unique=True, primary_key=True, verbose_name="id")
    mockName = models.CharField(max_length=255, null=False, default='',unique=True, verbose_name="mock名")
    # interface = models.ForeignKey(to=InterfaceModel, to_field='interface', related_name='related_mock', on_delete=models.SET(''), verbose_name='接口名' )
    interface_name_id = models.CharField(max_length=255, default='', verbose_name="关联接口")
    data = models.TextField(verbose_name="mock数据")
    author = models.CharField(max_length=50, null=False, default='', verbose_name='创建者')
    thirdpart = models.CharField(max_length=255, default='', verbose_name="三方接口")
    status = models.CharField(max_length=255, default='undefault', verbose_name="mock状态")
    status_code = models.IntegerField(default=200,verbose_name='状态码')
    timeout = models.IntegerField(default=0, verbose_name="超时时长")
    remarks= models.CharField(max_length=500, verbose_name="备注")
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")     class Meta:
        db_table = 't_mock_data'
        verbose_name ='mock表'
        verbose_name_plural= verbose_name     def get_object(self):
        """
        :return:
        """
        return self.mockName
# mock服务表
class MockServiceModel(models.Model):
    service = models.CharField(max_length=255, null=False, default='',unique=True, verbose_name="服务名")
    status = models.CharField(max_length=255, default='1' ,verbose_name="状态")
    author = models.CharField(max_length=50, null=False, default='', verbose_name='创建者')
    remarks = models.CharField(max_length=500, default='', verbose_name="备注")
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")     class Meta:
        db_table = 't_mock_service'
        verbose_name ='mock服务名表'
        verbose_name_plural= verbose_name     def __unicode__(self):
        return self.service # interface_mock_map表
class MockInterfaceMapModel(models.Model):
    interface = models.ForeignKey(to=InterfaceModel, to_field='interface', on_delete=models.SET(""), verbose_name="接口名")
    mockIds = models.CharField(max_length=500, default='',  verbose_name="关联mockId")
    remarks = models.CharField(max_length=500, default='', verbose_name="备注")
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")     class Meta:
        db_table = 't_mock_interface_map'
        verbose_name ='mock与接口对应关系表'
        verbose_name_plural= verbose_name

项目设计了基本的mock表、mock服务表、mock数据表、以及后续需要使用到的mock与接口关联表。表的生产参考django官网。

后台接口中主要有以下核心接口
1、根据接口名获取mock数据
@csrf_exempt
def get_mock_data_by_interface(self, request, interface_name, others=''):
    """
    根据接口名获取mock数据
    :return:
    """
    try:
        mock_data_id = MockModel.objects.filter(relate_interface=interface_name).values_list("mock_data_id")[0][0]
        mock_data, status_code, timeout= MockDataModel.objects.filter(id=mock_data_id).values_list("data", "status_code", "timeout")[0]
        mock_data = json.loads(mock_data)
        if(timeout!=0):
            time.sleep(int(timeout))
        logging.debug(mock_data)
    except Exception as e:
        logging.error("根据接口名获取mock数据失败,测试数据格式错误")
        logging.error(e)
        status_code = 500
        mock_data = {"msg":"系统错误","code":status_code}
    return JsonResponse(mock_data, status=int(status_code), safe=False, json_dumps_params={"ensure_ascii":False})

@csrf_exempt装饰器来标识一个视图可以被跨域访问,类中的装饰写法可以参考上面代码,当然也可以在url中实现如

from django.conf.urls import url
from django.views.decorators.csrf import csrf_exempt
import views
urlpatterns = [
    url(r'^myview/$', csrf_exempt(views.MyView.as_view()), name='myview'),
]
搜索mock数据, 模糊匹配
@csrf_exempt
def search(self, request):
    """
    搜索mock数据, 模糊匹配
    :return:
    """
    query_form = request.POST.dict()
    logging.debug(query_form)
    new_query_form = {}
    for key, value in query_form.items():
        # 如果值不为空,将该键值对保存到新字典中
        if key == "relate_interface" and value != '':
            new_query_form["relate_interface"] = value
        elif key == 'service' and value != '':
            new_query_form["service"] = value
        elif key == 'search_input_value' and value != '':
            new_query_form["search_input_value"] = value
        else:
            continue
    # 获取符合条件的mock数据
    logging.debug(new_query_form)
    # 如果搜索条件为空,则返回全部数据,否则按条件过滤
    if new_query_form == {}:
        objs = MockModel.objects.all().order_by("create_time")
    else:
        try:
            logging.debug("search_input_value搜索字段不为空")
            mockFilterStr = new_query_form["search_input_value"]
            del new_query_form["search_input_value"]
            objs = MockModel.objects.filter(relate_interface__contains=mockFilterStr).filter(
                **new_query_form).order_by('create_time')
        except KeyError:
            logging.debug("mockFilterStr搜索字段不存在")
            objs = MockModel.objects.filter(**new_query_form).order_by('create_time')
    # total总数据,page:可分页数, limit:每页限制数(0),objs:每页展示数(20)
    total = len(objs)
    if objs == None:
        response = {
            'code': 201,
            'msg': ' return None',
            'total': 0,
            'data': []
        }
    else:
        # 数据序列化
        objs = serializers.serialize("json", objs)
        obj_list = []
        for obj in eval(objs):
            obj_new = obj["fields"]
            try:
                obj_new['id'] = obj["pk"]
            except KeyError:
                continue
            obj_list.append(obj_new)
        response = {
            'code': 200,
            'msg': 'success',
            'total': total,
            'data': obj_list
        }
    return JsonResponse(response)

查询出来的数据是需要一定的数据处理的,接口内容中主要通过django.core的序列化方法objs = serializers.serialize("json", objs)进行处理。

mock数据删除接口
@staticmethod
def delete(request):
    """
    删除
    :return:
    """
    mock_data_id_list = request.POST.getlist("idList")
    logging.debug(mock_data_id_list)
    ids_string = ','.join(mock_data_id_list)if len(mock_data_id_list)>1 else mock_data_id_list[0]
    try:
        MockDataModel.objects.extra(where=['id IN ('+ ids_string +')']).delete()
        response = {
            'code': 200,
            'msg': '删除成功'
        }
    except KeyError:
        logging.warn("没有这个键")
        response = {
            'code': 201,
            'msg': '删除失败'
        }
    return JsonResponse(response)

数据删除这一块还是使用的自带orm操作,获取到批量的id列表,统一进行删除操作。

新增mock接口信息
@HD.decorate_create_model(MockModel)
def m_add_interface(self, request):
    '''
    新增mock接口信息
    :param request:
    :return:
    '''
    interfaceName = request.POST.get("t-addInterface")
    serverName = request.POST.get("s-mockServiceName")
    origin_url = request.POST.get("origin_url")
    logging.debug("接口名:{}".format(interfaceName))
    logging.debug("服务名:{}".format(serverName))
    if serverName and interfaceName:
        interfaceName = interfaceName.strip()
        serverName = serverName.strip()
        origin_url = origin_url.strip()
        return {"relate_interface":interfaceName, "service":serverName, "origin_url":origin_url}
    else:
        return {"msg": "接口名称或服务名为空,请重新输入", "code": 0}

以上为一些核心接口的代码设计

mock规则

后端接口的规划

在数据库的存储上,每一个接口信息可能对应多条返回数据,那么在实际请求一个接口时如何返回我们指定的数据呢?
1、我们将众多数据中的状态做一个标识,只保存一个有效状态,当我们需要返回某个数据时只需要修改为有效状态,而我们正是这样做的。

@csrf_exempt
def set_default_mock(self, request):
    """
    设置默认值
    :param mock_data_id:
    :return:
    """
    mock_data_id = request.POST.get("mock_data_id")
    # logging.debug(dict(MockModel.objects.filter(mock_data_id=mock_data_id)))
    try:
        self.set_mock_data_status(mock_data_id)
        response = {
            "code": 200,
            'msg': "设置成功",
            'data': [],
        }
    except Exception as e:
        logging.debug("没有对应的数据")
        response = {
            "code": 302,
            'msg': "设置失败",
            'data': e,
        }
    logging.debug(response)
    return JsonResponse(response)

2、在我们需要对某个接口进行请求获取mock数据时增加一个数据id的入参,返回数据时根据入参的数据id进行返回。
每一个接口对应多条mock数据,那么如果很多接口势必会导致在加载数据时前端获取开发的接口返回时间过长,渲染后给用户的感觉会有所延迟,那么如何提高给前端接口数据返回的速度呢?
1、前端增加分页处理,每次获取数据时返回当前页的数据,本项目就是这样处理的

@staticmethod
def decorate_http_table_response(ModelName):
    """
    :return:
    """
    def wrapper(func):
        def inner(*args, **kwargs):
            request_dict = func(*args, **kwargs)
            id = request_dict.get("id")
            #判断是根据id获取单条数据还是根据page和limit获取数据
            if id:
                objs = ModelName.objects.filter(id=request_dict['id'])
                total = 1
            else:
                #如果没有id,则根据传入的page和limit获取对应数据,page 和 limit 均默认为1
                page = request_dict.get("page") if (request_dict.get("page")) else 1
                limit = request_dict.get("limit") if (request_dict.get("limit")) else 20
                objs_all = ModelName.objects.get_queryset().order_by('-id')
                #total总数据,page:可分页数, limit:每页限制数,objs:每页展示数
                logging.debug(objs_all)
                total = len(objs_all)
                p = Paginator(objs_all, limit, 0)
                objs = p.page(page).object_list
            if objs == None:
                response = {
                    'code': 201,
                    'msg': ' return None',
                    'total': 0,
                    'data': []
                }
            else:
                #数据序列化
                objs = serializers.serialize("json", objs)
                obj_list = []
                for obj in eval(objs):
                    obj_new = obj["fields"]
                    try:
                        obj_new['id']=obj["pk"]
                    except KeyError:
                        continue
                    obj_list.append(obj_new)
                response = {
                    'code': 200,
                    'msg': 'success',
                    'total': total,
                    'data': obj_list
                }
                logging.debug(response)
            return JsonResponse(response)
        return inner
    return wrapper

通过特定的前端传参来标识当前是第几页,当前也需要返回几条数据,最后数据以二维数组的形式返回。
2、通过增加redis缓存,同时按方法1获取部分数据量来达到接口的快速返回。

异常处理

延时设置

在软件测试的实际应用中,经常会出现这么种情况,就是B接口依赖于A接口的返回,那么在测试的时候假设要模拟A接口出现异常返回很慢的情况时,又要如何开发mock呢?
这种场景的实现相对来说比较简单基本就是增加一个接口等待,延时返回数据,本项目中通过获取用户保存的延时时间,使用sheep方法执行等待。

状态码设置

关于返回的状态码,是直接通过修改返回的状态码来实现的,这一个相对较为简单不加说明。

return JsonResponse(mock_data, status=int(status_code), safe=False, json_dumps_params={"ensure_ascii":False})
不同类型数据返回

软件测试中对于mock的接口返回数据类型经常会出现特殊的需求,比如返回图片、xml、json等,那么又要如何开发呢?
首先数据库我们需要新增字段,字段类型分别为image、clob类型,将图片数据以二进制数据流的方式存放在image类型字段下,将xml、html数据存放在clob字段类型下。
在数据返回时对返回类型进行判断,然后对数据进行返回。

def index(request):  
    if request.GET["type"] == "img":  
        return HttpResponse(open("test.png","rb"),content_type="image/png")  
        ## 这里 返回图片  
    elif request.GET["type"] == "html":  
        return HttpResponse(open("1.html","rb"),content_type="text/html")  
        ## 返回 html文本  
    elif request.GET["type"] == "xml":  
        return HttpResponse(open("1.html","rb"),content_type="text/xml")  
        ##返回 xml文本  
    elif request.GET["type"] == "json":  
        return HttpResponse({"code":"ok"},content_type="application/json")  
        ##返回 json文本 

更多内容关注微信公众号 软件测试微课堂

在django中如何从零开始搭建一个mock服务的更多相关文章

  1. Java进阶专题(二十二) 从零开始搭建一个微服务架构系统 (上)

    前言 "微服务"一词源于 Martin Fowler的名为 Microservices的,博文,可以在他的官方博客上找到http:/ /martinfowler . com/art ...

  2. vue-用Vue-cli从零开始搭建一个Vue项目

    Vue是近两年来比较火的一个前端框架(渐进式框架吧). Vue两大核心思想:组件化和数据驱动.组件化就是将一个整体合理拆分为一个一个小块(组件),组件可重复使用:数据驱动是前端的未来发展方向,释放了对 ...

  3. 从零开始搭建一个react项目

    Nav logo 120 发现 关注 消息 4 搜索 从零开始搭建一个react项目 96 瘦人假噜噜 2017.04.23 23:29* 字数 6330 阅读 32892评论 31喜欢 36 项目地 ...

  4. 从零开始搭建一个简单的基于webpack的vue开发环境

    原文地址:https://segmentfault.com/a/1190000012789253?utm_source=tag-newest 从零开始搭建一个简单的基于webpack的react开发环 ...

  5. 从零开始搭建一个PaaS平台 - 我们要做什么

    前言 从最开始的小公司做小网站,到现在进入现在的公司做项目,发现小公司里很多很多工作都是重复的劳动(增删改查),不过想想也是,业务软件最基础的东西不就是增删改查吗. 但是很多时候,这种业务逻辑其实没有 ...

  6. 搭建一个web服务下载HDFS的文件

    需求描述 为了能方便快速的获取HDFS中的文件,简单的搭建一个web服务提供下载很方便快速,而且在web服务器端不留临时文件,只做stream中转,效率相当高! 使用的框架是SpringMVC+HDF ...

  7. 利用OpenStreetMap(OSM)数据搭建一个地图服务

     http://www.cnblogs.com/LBSer/p/4451471.html 图 利用OSM数据简单发布的北京地图服务   一.OSM是什么 开放街道图(OpenStreetMap,简称O ...

  8. 通过express快速搭建一个node服务

    Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台.可以理解为是运行在服务端的 JavaScript.如果你是一个前端程序员,不太擅长像PHP.Python或Ruby等 ...

  9. Django1.8教程——从零开始搭建一个完整django博客(一)

    第一个Django项目将是一个完整的博客网站.它和我们博客园使用的博客别无二致,一样有分类.标签.归档.查询等功能.如果你对Django感兴趣的话,这是一个绝好的机会.该教程将和你一起,从零开始,搭建 ...

随机推荐

  1. 查看网卡信息 - ethtool

    查看网卡是千兆还是万兆网卡,使用ethtool 网络接口名 ethtool eth0

  2. 《前端之路》--- 重温 Egg.js

    目录 <前端之路>--- 重温 Egg.js 一.基础功能 > 日志系统包含了 四大层面的 日志对象, 分别是 App Logger.App CoreLogger.Context L ...

  3. 并查集(不相交集)的Remove操作

    给并查集(不相交集)的添加一个\(Remove(X)\)操作,该操作把\(X\)从当前的集合中除去并把它放到自己的集合中. 实现思想 英文原句 We assume that the tree is i ...

  4. 7-36 jmu-python-统计字符个数 (10 分)

    输入一个字符串,统计其中数字字符及小写字符的个数 输入格式: 输入一行字符串 输出格式: 共有?个数字,?个小写字符,?填入对应数量 输入样例: helo134ss12 输出样例: 共有5个数字,6个 ...

  5. 【转】css样式自动换行(强制换行)

    原文链接:http://blog.csdn.net/ye987987... 自动换行问题,正常字符的换行是比较合理的,而连续的数字和英文字符常常将容器撑大,挺让人头疼,下面介绍的是CSS如何实现换行的 ...

  6. Java的三魂七魄 —— 高级多线程

    目录 Java的三魂七魄 -- 高级多线程 一.多线程的创建 二.线程安全问题 三.线程通信问题 四.更多实例 1.用线程同步的方法解决单例模式的线程安全问题 2.银行存钱问题(线程安全问题) 3.生 ...

  7. 牛客网剑指offer第34题——找到第一个只出现一次的字符

    题目如下: 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写). 先上代码: class ...

  8. python学习(二)之turtle库绘图

    今天是三月七号,也就是女生节,或者女神节.不知道你是不是有自己喜欢的女孩子,在这里你可以用turtle库绘制一朵玫瑰花,送给你喜欢的姑娘.(拉到最后有惊喜哦)但在画这朵玫瑰花之前,先来一个基础的图形, ...

  9. spring boot actuator服务监控与管理

    1.引入actuator所需要的jar包 <dependency> <groupId>org.springframework.boot</groupId> < ...

  10. C# BASS音频库 + 频谱基本用法

    效果图: 使用了 BASS.dll.  BASS.NET.dll   和  PeakMeterCtrl.dll 前面两个负责播放   最后一个负责绘制频谱,本文重点讲的是频谱部分,播放音频部分注意一点 ...