接口自动化-httprunner3引入基于契约校验响应数据

背景

在进行接口自动化时,需要对返回数据进行校验。
在响应数据中字段少的时候,可以对每个字段进行校验。但当响应数据中字段多时,单独写校验比较麻烦。
引入基于契约的校验,可以对返回数据进行基于值或者类型的全量校验。

安装

pip install pactverify

python类契约

from pactverify.matchers import Matcher, Like, EachLike, Enum, Term, PactVerify

# 定义契约格式
expect_format = Matcher({
    'code': 0,  #  key存在,值相等,code==0
    'msg': 'success',  #  key存在,值相等,msg=='success'
    # [{}]列表嵌套字典
    'data': EachLike({
        "type_id": 249,  # type_id key存在,值类型相等,type(type_id) == type(249)
        "name": "王者荣耀",  # name key存在,值类型相等,type(name) == type("王者荣耀")
    }),
    'type': Enum([11,22]), #枚举类型,值为11或者22
    'list': EachLike(11,minimum=2) #列表,
})

# 实际返回数据
actual_data = {
    "msg": "success",
    "code": 1,
    'type': 12,
    "data": [{
        # type_id类型不匹配
        "type_id": '249',
        "name": "王者荣耀"
    }, {
        # 缺少name
        "type_id": 250,
    }, {
        # 比契约定义多index字段
        "type_id": 251,
        "name": "刺激战场",
        "index": 111
    }
    ],
    'list': [11]
}
# hard_mode默认为true,hard_mode = True时,实际返回key必须严格等于预期key;
#hard_mode = False时,实际返回key包含预期key即可
mPactVerify = PactVerify(expect_format, hard_mode=True)
# 校验实际返回数据
mPactVerify.verify(actual_data)
# 校验结果  False
print(mPactVerify.verify_result)
''' 校验错误信息
错误信息输出actual_key路径:root.data.0.name形式
root为根目录,dict类型拼接key,list类型拼接数组下标(从0开始)
{   
    # 实际key少于预期key错误
    'key_less_than_expect_error': ['root.data.1.name'],
    # 实际key多与预期key错误,只在hard_mode = True时才报该错误
    'key_more_than_expect_error': ['root.data.2.index'],
    # 值不匹配错误
    'value_not_match_error': [{
            'actual_key': 'root.code',
            'actual_value': 1,
            'expect_value': 0
        }
    ],
    # 类型不匹配错误
    'type_not_match_error': [{
            'actual_key': 'root.data.0.type_id',
            'actual_vaule': '249',
            'expect_type': 'int'
        }
    ],
    # 数组长度不匹配错误
    'list_len_not_match_error': [{
            'actual_key': 'root.list',
            'actual_len': 1,
            'min_len': 2
        }
    ],
    # 枚举不匹配错误
    'enum_not_match_error': [{
            'actual_key': 'root.type',
            'actual_value': 12,
            'expect_enum': [11, 22]
        }
    ]
}

'''
print(mPactVerify.verify_info)

引入Httprunner

pacts.py存放接口契约

项目根目录新增pacts.py文件,用来存放接口的Python类契约规则

from pactverify.matchers import Matcher, Like, EachLike, Term, Enum, PactVerify


class Pact:
    # /model/custom/type/query接口契约
     model_custom_type_query= Matcher(
        {
            "success": 1,#值匹配
            "code": 1001,#值匹配
            "msg":"请求成功",#值匹配
            "data": EachLike( {#数组匹配
            "createTime": 20230312115756,#类型匹配
            "modifyTime": 20230312115756,
            "code": 15,
            "modelId": "bee2ea6ec1a7439480995e80ae12b139",
            "modelType": "Placement"
        }),
            "requestId": Like("5a3664c2-f502-4ac0-8f38-b294b671e0a6")#类型匹配

        }
    )

debugtalk.py加入处理函数

def pactverify(response, pact_name):
    if not hasattr(Pact, pact_name):
        raise PactVerifyError('{}契约未找到'.format(pact_name))


    rsp_json = response.resp_obj.json()

    expect_format = getattr(Pact, pact_name)
    mPactVerify = PactVerify(expect_format)
    mPactVerify.verify(rsp_json)

    response.verify_result = mPactVerify.verify_result #将校验结果写入reponse
    response.verify_info = mPactVerify.verify_info
    if  response.verify_result==False:
        logger.info(f"契约校验失败{mPactVerify.verify_info}")

传入pact_name,即接口契约名称,如果Pact类中存在该契约,则将契约内容与response进行比对,并将结果写给reponse的verify_result,将校验信息写给response的verify_info

reponse.py源码修改

在pactverify方法里,将校验结果和信息写给了response,但是这两个字段在httprunner里没有定义。
所以需要在httprunner的response.py中加入定义
image.png

测试用例写法


config:
    name: 模型类型查询
    base_url: ${ENV(base_url)}

teststeps:
    - name: "模型类型查询"
      setup_hooks:
        - ${custom_reports(模型管理, 模型类型查询)} #根据id查询所属类型
      request:
        method: POST
        url: /model/custom/type/query
        headers:
            cookie: ${ENV(cookie)}
        json:
            ["bee2ea6ec1a7439480995e80ae12b139"]
      teardown_hooks:
        # teardown处理,将verify_result和verify_info写入response对象
        - ${pactverify($response,model_custom_type_query)}
      validate:
        -   eq: [ status_code, 200 ] # 检验响应状态码是否等于200
        -   eq: [ verify_result,True ]

在测试用例步骤中加入teardown_hooks,调用pactverify方法,传入该接口的契约名称进行校验。
在validate步骤中,checkitem写为verify_result,调用reponse中的verify_result字段,进行断言。
image.png
执行用例,查看打印,契约校验已经进行了判断。
契约校验结果为失败,原因是响应中比契约规则多了一个msg字段。

附录-匹配规则

Match类-值匹配

expect_format_1=Matcher(11) #预期结果为11

预期返回数据actual为dict结构,actual[‘k1’] == ‘v1’ python类契约格式

expect_format_4=Matcher({'k1':'v1'})

Like类-类型匹配

# 预期type(11) python类契约
 expect_format_1=Like(11)
# 预期返回数据actual为dict结构,actual['k1'] == type('v1') python类契约 
expect_format_4=Like({'k1':'v1'})

EachLike 类-数组匹配

# 预期[type(11)] python类契约 
expect_format_1=EachLike(11)
# 预期[Like{'k1':'v1'}]或[],minimum为数组最小长度,默认minimum=1 python类契约 
expect_format_5=EachLike({'k1':'v1'},minimum=0)


Term类-正则匹配

# 预期r'^\d{2}$',并且type(actual_data) == type(example),example也用来测试正则表达式  python类契约
expect_format_1 = Term(r'^\d{2}$', example=11)

# 预期r'^\d{2}$',example用来测试正则表达式,type_strict = False时跳过对example参数类型校验   python类契约
expect_format_2 = Term(r'^\d{2}$', example="11", type_strict=False)

Enum类-枚举匹配

# 预期11或22  python类契约
expected_format_1 = Enum([11, 22])


# iterate_list为true时,当目标数据为数组时,会遍历数组中每个元素是否in [11, 22]  python类契约
expected_format_2 = Enum([11, 22], iterate_list=True)

复杂规则匹配

1.{{}}格式

actual_data = {
    'code': 0,
    'msg': 'success',
    'data': {
        "id": 1,
        "name": 'lili'
    }
}

# python类契约
expect_format = Like({
    'code': 0,
    'msg': 'success',
    'data': Like({
        "id": 1,
        "name": 'lili'
    })
})

2.[[]] 格式

actual_data = [[{
    "id": 1,
    "name": 'lili'
}]]

# python类契约
expect_format = EachLike(EachLike({
    "id": 1,
    "name": 'lili'
}))

3.{[]}格式

actual_data = {
    'code': 0,
    'msg': 'success',
    'data': [{
        "id": 1,
        "name": 'lili'
    },{
        "id": 2,
        "name": 'lilei'
    }]
}

# python类契约
expect_format = Like({
    'code': 0,
    'msg': 'success',
    'data': EachLike({
        "id": 1,
        "name": 'lili'
    })
})


4.Like-Term 嵌套

actual_data = {
    'code': 0,
    'msg': 'success',
    'data': {
        "id": 1,
        "name": 'lili'
    }
}

# python类契约
expect_format = Like({
    'code': 0,
    'msg': 'success',
    'data': Like({
        "id": 1,
        "name": Term(r'\w*', example='lili')
    })
})

}

5.Like-Matcher 嵌套

actual_data = {
    'name': 'lilei',
    'age': 12
}

# python类契约
expect_format = Like({
    # name字段值类型匹配
    'name': 'lilei',
    # age字段值匹配
    'age': Matcher(12),
})

说明:

  1. Matcher,Like 和 EachLike 类可以不限层级嵌套,Term 和 Enum 则不能嵌套其他规则
  2. 匹配规则多层嵌套时,内层规则优先生效

异常场景匹配

1.null 匹配

# nullable为true时允许返回null,预期null和(actual为dict结构,actual['k1'] == 'v1' or null)形式   python类契约
expect_format = Matcher({'k1': 'v1'}, nullable=True)


# nullable为true时允许返回null,预期null和(actual为dict结构,actual['k1'] == type('v1') or null)形式   python类契约
expect_format = Like({'k1': 'v1'}, nullable=True)


# nullable为true时允许返回null,预期null和[null,{'k1':null}]形式   python类契约
expect_format = EachLike({'k1': 'v1'}, nullable=True)


# nullable为true时允许返回null,预期null和11形式   python类契约
expect_format = Term(r'^\d{2}$', example=11, nullable=True)

}

# nullable为true时允许返回null,预期null和11/22/33形式   python类契约
expect_format = Enum([11, 22, 33], nullable=True)
# nullable为true时允许返回null,预期null和11/22/33形式   json契约

备注:nullable 参数在 hard_mode = True 时也生效

2.{}匹配

# dict_emptiable为true时,允许返回{},预期{}和(actual为dict结构,actual['k1'] == 'v1')形式   python类契约
expect_format = Matcher({'k1': 'v1'}, dict_emptiable=True)

# dict_emptiable为true时,允许返回{},预期{}和(actual为dict结构,actual['k1'] == type('v1'))形式   python类契约
expect_format = Like({'k1': 'v1'}, dict_emptiable=True)

备注:dict_emptiable 在 hard_mode = True 时也生效

3.json 格式字符串匹配

# actual为"{\"k1\":\"v1\"}"json字符串格式时,先进行json.loads再校验   python类契约
expect_format = Matcher({'k1': 'v1'}, jsonloads=True)


# actual为"{\"k1\":\"v1\"}"json字符串格式时,先进行json.loads再校验   python类契约
expect_format = Like({'k1': 'v1'}, jsonloads=True)
# actual为"{\"k1\":\"v1\"}"json字符串格式时,先进行json.loads再校验   json契约


# actual为"[{\"k1\":\"v1\"}]"json字符串格式时,先进行json.loads再校验  python类契约
expect_format = EachLike({'k1': 'v1'}, jsonloads=True)
# actual为"[{\"k1\":\"v1\"}]"json字符串格式时,先进行json.loads再校验  json契约

# actual为"[11,22]"json字符串格式时,先进行json.loads再校验   python类契约
expected_format = Enum([11, 22], jsonloads=True)
# actual为"[11,22]"json字符串格式时,先进行json.loads再校验   json契约

}

4.key 不存在匹配

# key_missable为true时,允许key不存在,key存在时走正常校验;Matcher,Like,EachLike,Term和Enum类都可使用该属性   python类契约
expect_format = Matcher({
    'code': Like(0, key_missable=True),
    'msg': Matcher('success', key_missable=True),
    'data': EachLike(11, key_missable=True),
    'age': Term(r'^\d{2}$', example=11, key_missable=True),
    'num': Enum([11, 22, 33], key_missable=True)
})

      

# dict_key_missable为true时,允许dict结构中的key不存在,但key不能多(hard_mode=true时),key存在时正常校验  python类契约
expected_format = Matcher({
    'name': 'lilei',
    'age': 12,
    'sex': 'man'
}, dict_key_missable=True)


# dict_key_missable为true时,允许dict结构中的key不存在,但key不能多(hard_mode=true时),key存在时正常校验   python类契约
expected_format = Like({
    'name': 'lilei',
    'age': 12,
    'sex': 'man'
}, dict_key_missable=True)


# dict_key_missable为true时,允许dict结构中的key不存在,但key不能多(hard_mode=true时),key存在时正常校验   python类契约
expected_format = EachLike({
    'name': 'lilei',
    'age': 12,
    'sex': 'man'
}, dict_key_missable=True)
# dict_key_missable为true时,允许dict结构中的key不存在,但key不能多(hard_mode=true时),key存在时正常校验   json契约

5.多类型匹配

# actual数据为type(11)或type('11'),extra_types可以添加多个示例数据,对基础数据类型(int,float,boolean,str,None)示例有效,对list dict等类型无效  python类契约
expect_format = Like(11, extra_types=['11'])

# actual数据为[type(11)]或[type('11')],extra_types可以添加多个示例数据,对基础数据类型示例(int,float,boolean,str,None)有效,对list dict等类型无效  python类契约
expect_format = EachLike(11, extra_types=['11'])

6.非强制字段匹配

expect_format = Like({'k1': 'v1'})
# hard_mode=False只匹配契约中定义的字段,实际返回的多余字段忽略    python类契约
mPactVerify = PactVerify(expect_format, hard_mode=False)
actual_data = {'k1': 'v1', 'k2': 'v2'}
# 只校验k1字段,k2字段忽略
mPactVerify.verify(actual_data)


# hard_mode=False只匹配契约中定义的字段,实际返回的多余字段忽略   json契约
mPactJsonVerify = PactJsonVerify(expect_format, hard_mode=False)
actual_data = {'k1': 'v1', 'k2': 'v2'}
# 只校验k1字段,k2字段忽略
mPactJsonVerify.verify(actual_data)

备注:
1. key_missable 在 hard_mode = True 时也生效
2. key_missable 针对 actual_data 本身的 key,dict_key_missable 针对 actual_data 字典中的 key,可以同时生效
注意:异常匹配场景越多,代表接口数据格式越不规范

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值