引言
我在编写接口测试用例时发现,我们如果使用unittest 提供的原生断言方法的断言成本很高,而且代码可读性变得比较差,因此我想在common模块中对断言方法进行封装。
## 案例
def testCase01_major(self):
"""新建分组主流程测试用例"""
host = 'http://******'
url = host + '******************************'
user_id = 372199862
headers = {
'x-user-key': str(user_id),
'cookie:' **********'
}
group_id = str(int(time.time()))
group_name = f'group_{int(time.time())}'
body = {
'groupId': group_id,
'groupName': group_name,
'order': 0
}
res = requests.post(url=url, headers=headers, json=body)
self.assertEqual(200, res.status_code)
self.assertTrue('responseTime' in res.json().keys())
self.assertTrue('updateTime' in res.json().keys())
self.assertTrue(len(res.json().keys()) == 2)
self.assertTrue(type(res.json()['responseTime']) == int)
self.assertTrue(type(res.json()['updateTime']) == int)
在上述代码中,我基于请求行营的状态码,以及响应体中参数进行了校验。从以上代码来看,存在的问题有:断言代码冗余,断言失败后不能明确具体是什么问题,复用性差等问题。 所以,基于以上问题,需要优化断言方法,将断言方法封装。
断言方法封装思路
首先,我们需要明确封装断言方法要实现的目标:
支持多层级字段校验(基础类型、嵌套字典、列表结构)
灵活控制校验严格性(比如是否允许多余字段)
其次,基于断言方法封装的目标,断言方法封装思路主要就是进行返回体中json字符串的对齐。
因此,返回体需要校验的内容如下:
接口文档中所描述的返回字段是否存在
确保返回体中所有字段都能成功返回。
返回体中不存在除接口文档外的多余字段
返回体中字段的数据类型
对于已知并且有基准值的字段,可以直接判断其基准值。
数据源校验,前置构建的数据和查询到的数据需要一一对齐
“子对象”也需要校验
例如,部分返回体中存在嵌套列表结构的数据时,我们需要对嵌套列表中的内容也进行校验。
断言方法封装步骤
step1: 定义期望返回体的协议
基于在“引言”中给出的案例,我们可以根据实际返回体定义出 期望的返回体如下:
{
"responseTime":long,
"updateTime": long
}
【注意事项】
在定义期望对齐返回体时需要明确返回体中的字段和结构
仅需要校验数据类型的字段时,在预期返回体对应参数的值直接描述数据类型,比方说 "responseTime": int
需要同时校验数据类型和精准度值的字段时,直接在预期返回体对应参数的值描述精准值即可,比方说"responseTime": 1
如果存在嵌套结构,也需要一一对齐数据结构和值描述在预期值当中。
step2:实现方法框架以及字段的遍历
基于unittest框架使用unittest下的断言方法实现封装。
import unittest
class AssertCommon(unittest.TestCase):
def func(self, expect, actual):
"""
json通用断言方法
:param expect: 定义预期返回体
:param actual: 实际返回的json
:return: 断言成功返回None,断言失败触发fail
"""
for key, value in expect.items():
self.assertIn(key, actual.keys())
step3: 检测是否存在协议外的其他字段
在step2 中的判断,只能校验出返回体中存在的字段,但是对于返回体中多余的字段无法判断。
# 校验是否存在多余的字段
self.assertEqual(len(expect.keys()), len(actual.keys()), msg=f'response keys len different, response keys have: {list(actual.keys())}')
step5:实现字段类型的校验
需要先判断预期中值是否为类型,才进行类型校验
for key, value in expect.items():
# 进行数据类型的校验
if isinstance(value, type):
self.assertEqual(value, type(actual[key]),
msg=f'{key} type error! actual type is {str(type(actual[key]))}')
step6:实现字段精准值的校验
else:
self.assertEqual(value, actual[key],
msg=f'{key} value error! actual value is {str(actual[key])}')
step7:遇到嵌套的列表结构时
也需要进行类型的校验和精准值的校验,根据列表索引找到对应值
elif isinstance(value, list):
for i in range(len(value)):
if isinstance(value[i], type):
self.assertEqual(value[i], type(actual[key][i]),
msg=f'list element {actual[key][i]} type different, actual response {actual[key]}')
else:
self.assertEqual(value[i], actual[key][i],
msg=f'list element {actual[key][i]} value different, actual response {actual[key]}')
step8:遇到列表字典的嵌套结构,需要实现递归
elif isinstance(value, list):
for i in range(len(value)):
if isinstance(value[i], type):
self.assertEqual(value[i], type(actual[key][i]),
msg=f'list element {actual[key][i]} type different, actual response {actual[key]}')
elif isinstance(value[i], dict):
self.func(value[i], actual[key][i])
总结
经过断言封装后,在测试用例中我们可以通过直接调用AssertCommon().fun() 实现断言,而不需要在每个用例中都执行self.assertin/ self.assertEqual等方法。进而避免了代码冗余。而且在之后如果返回体有更新时,测试用例也易于维护。
# 封装前:冗余断言
self.assertEqual(200, res.status_code)
self.assertTrue('responseTime' in res.json().keys())
...
# 封装后:单行调用
AssertCommon().func(expect.res, res.json())