其他测试框架 基于 Flask 的简易 Mock 平台

尹全旺 · 2018年06月21日 · 最后由 ZOO 回复于 2021年05月21日 · 4190 次阅读

Mock Server

基于 Flask 实现的一个简易 Mock 平台,使用标准 json 结构体编写 Mock Api https://github.com/yinquanwang/MockServer

Key Features

  • 遵循 Http 协议,支持GETPOSTPUTDELETE 常用 http 请求方式
  • mock 结构体为标准 json 结构,支持参数内容规则制定以及对象返回信息
  • 内置常用校验器,equals、contains、between、length、type, 可灵活自己扩展
  • 支持自定义报文 Valid 与 Invalid,返回报文可以是任意类型
  • 数据类型丰富,支持 string、int、float、list、dict

得益于 Flask 强大的路由系统,设计核心就是将所有请求的 path 归一化到一个视图函数处理:

@app.route('/<path:path>', methods=['GET', 'PUT', 'DELETE', 'POST'])
def dispatch_request(path):
    """
    mock view logic
    :param path: request url for mock server
    :return: response msg that use default or custom defined
    """
    m = models.Api.query.filter_by(url=request.path, method=request.method).first_or_404()
    body = json.loads(m.body)
    return domain_server(**body)


@app.errorhandler(404)
def url_not_found(error):
    return json.dumps({
        "status": 404,
        "msg": "the request url not found,please check"
    })

如果 mock 脚本不自定义返回报文,由系统默认提供,默认标准 json 结构体

VALID = {
    "success": True,
    "code": "0000",
    "msg": "success"
}

INVALID = {
    'code': '0020',
    'msg': 'request data invalid',
    'success': False
}

MISS = {
    'code': '0021',
    'msg': 'request data missed',
    'success': False
}

TYPE_NOT_MATCH = {
    "success": False,
    "code": "0001",
}

EQUALS = {
    "success": False,
    "code": "0005",
}

NOT_BETWEEN = {
    "success": False,
    "code": "0007",
}

STR_NOT_CONTAINS = {
    "success": False,
    "code": "0004",
}

STR_TOO_LONG = {
    "success": False,
    "code": "0006",
}

由此我们必须实现一个校验器,对各个字段进行一些基本校验

class Validator:
    """
    Validator for mock check
    """

    @classmethod
    def valid(cls, response=None):
        return get_response(response, VALID)

    @classmethod
    def type_not_match(cls, type, data, response=None):
        msg = '{data} must be {type} type'.format(data=data, type=type)
        TYPE_NOT_MATCH['msg'] = msg
        if type == 'int':
            if not isinstance(data, int):
                return get_response(response, TYPE_NOT_MATCH)
        elif type == 'float':
            if not isinstance(data, float):
                return get_response(response, TYPE_NOT_MATCH)
        elif type == 'string':
            if not isinstance(data, str):
                return get_response(response, TYPE_NOT_MATCH)
        elif type == 'bool':
            if not isinstance(data, bool):
                return get_response(response, TYPE_NOT_MATCH)
        elif type == 'list':
            if not isinstance(data, list):
                return get_response(response, TYPE_NOT_MATCH)
        elif type == 'dict':
            if not isinstance(data, dict):
                return get_response(response, TYPE_NOT_MATCH)
        else:
            return False

    @classmethod
    def is_not_equals(cls, data, expect, response=None):
        if data != expect:
            msg = '{data} must be equals {expect}'.format(data=data, expect=expect)
            EQUALS['msg'] = msg
            return get_response(response, EQUALS)
        else:
            return False

    @classmethod
    def is_not_between(cls, data, between, response=None):
        try:
            min = between[0]
            max = between[1]
        except IndexError:
            return {'msg': 'mock config error'}
        if data > max or min < min:
            msg = '{data} must be between in {between}'.format(data=data, between=between)
            NOT_BETWEEN['msg'] = msg
            return get_response(response, NOT_BETWEEN)
        else:
            return False

    @classmethod
    def is_not_contains(cls, data, expect, response=None):
        if data not in expect:
            msg = '{data} not in {expect}'.format(data=data, expect=expect)
            STR_NOT_CONTAINS['msg'] = msg
            return get_response(response, STR_NOT_CONTAINS)
        else:
            return False

    @classmethod
    def is_too_long(cls, data, length, response=None):
        if len(data) > length:
            msg = '{data} is  too long, max length is {length}'.format(data=data, length=length)
            STR_TOO_LONG['msg'] = msg
            return get_response(response, STR_TOO_LONG)
        else:
            return False

接下来便是对请求数据与期望数据匹配,从而返回相应报文了

def domain_server(**kwargs):
    """
    used for POST PUT DELETE
    :param kwargs: standard json mock scripts
    :return: response msg
    """
    data = kwargs.get('data', {})
    invalid = kwargs.get('invalid', {})

    if request.json:
        form = request.json

    elif request.form:
        form = request.form

    elif request.args:
        form = request.args

    if data is {}:  # do not have any parameters
        return Validator.valid(response=kwargs.get('valid'))

    else:
        if len(form) != len(data):  # data do not matched
            return json.dumps(MISS, ensure_ascii=False)

        for key in form.keys():
            if key not in data.keys():
                return json.dumps(INVALID, ensure_ascii=False)

        for key, value in form.items():  # usually validators
            expect = data.get(key)
            type = expect.get('type')
            msg = Validator.type_not_match(type, value, response=invalid.get('type'))
            if msg:
                return msg

            contains = expect.get('contains')
            if contains:
                msg = Validator.is_not_contains(value, contains, response=invalid.get('contains'))
                if msg:
                    return msg

            equals = expect.get('equals')
            if equals:
                msg = Validator.is_not_equals(value, equals, response=invalid.get('equals'))
                if msg:
                    return msg

            long = expect.get('long')
            if long:
                msg = Validator.is_too_long(value, long, response=invalid.get('length'))
                if msg:
                    return msg

            between = expect.get('between')
            if between:
                msg = Validator.is_not_between(value, between, response=invalid.get('between'))
                if msg:
                    return msg

        return Validator.valid(response=kwargs.get('valid'))

指导说明

  1. example for get request json { "data": { "password": { "equals": "lcc", "type": "string" }, "username": { "equals": "lcc", "type": "string" } }, "invalid": { "equals": { "msg": "恭喜啊,查询到该账号了" }, "type": "类型不匹配啊" }, "method": "GET", "name": "查询接口", "url": "/get/", "valid": { "msg": "查询成功啦", "success": true } } 例如上述 json 结构体,此 get 请求有两个参数 username,password, type=string,equals=lcc ,规定了 valid 报文和 invalid 中的 equals 报文

  1. example for post 支持 json 和 form,系统会自动进行分辨 json { "data": { "email": { "equals": { "lcc": "lcc@qq.com", "yqw": "yqw@qq.com" }, "type": "dict" }, "password": { "type": "string" }, "username": { "equals": "lcc", "length": 10, "type": "string" } }, "invalid": { "equals": { "msg": "哥们,类型不匹配啊" }, "type": "类型不匹配啊" }, "method": "POST", "name": "注册接口", "url": "/register/", "valid": { "msg": "注册成功啦" } }
  2. example for put json { "data": { "identity": { "equals": "1", "type": "string" } }, "invalid": { "equals": { "msg": "啊哦 该账号没有记录哦" }, "type": "类型不匹配啊" }, "method": "put", "name": "更新接口", "url": "/put/", "valid": { "msg": "查询成功啦", "success": true } }
  3. example for delete json { "data": { "account": { "equals": "lcc123456", "length": 10, "type": "string" } }, "invalid": { "equals": { "msg": "啊哦 该账号没有记录哦" }, "type": "类型不匹配啊" }, "method": "delete", "name": "删除接口", "url": "/del/1/", "valid": { "msg": "删除成功啦", "success": true } } ## 由于第一次接触 Flask,mock 的理解自己可能也有偏差,所以还请各位大神不要见怪

本地开发环境部署

  1. git clone 或者 checkout 至本地目录
  2. 修改:MockServer/config.py 数据库相关配置 python USERNAME = 'root' PASSWORD = 'lcc123456' HOST = '127.0.0.1' DB = 'MockServer'
  3. 安装相应依赖库 bash pip install -r requirements.txt
  4. 创建 MockServer 数据库, 默认 DB 是 MockServer
  5. 生成数据库迁移脚本,应用表结构 bash python manage.py db init python manage.py db migrate python manage.py db upgrade
  6. Start Server bash python run.py
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 4 条回复 时间 点赞
仅楼主可见
j · #4 · 2020年07月08日
仅楼主可见

python 版本是什么,mysql-connector 装不了

依赖包 mysql-connector 改成 mysql-connector-python

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册