Unittest 是 Python 原生自带的基础框架,是一种单元测试框架,提供用例的编写方法、断言方法、测试夹具、批量执行、结果汇总能力,用于接口自动化测试会存在一定的不便,比如说存在编码效率低、无日志文件、缺少数据隔离、缺少多条用例的参数化等,但是在 unittest 这样的原生框架下我们可以更方便的进行二次开发,本文主要聊聊通用断言方法的封装思路。
断言方法封装的核心思路:预期和实际 json 字符串的对齐
返回体需要校验内容:
接口文档中所描述的返回字段是否存在
返回体中不存在除接口文档外的多余字段
返回体中字段的数据类型
数据对齐:前置构建的数据和查询到的数据需要对齐
“子对象”也需要校验
断言方法实现的步骤
step1:先定义好期望返回体的协议
对齐返回体的字段和结构
仅需要校验数据类型的字段时,在预期返回体对应参数的值直接描述数据类型,比方说 "userId": int
需要同时校验数据类型和精准值的字段时,直接在预期返回体对应参数的值描述精准值即可,比方说"userId": 1
如果存在嵌套结构,也需要一一对齐数据结构和值描述在预期值当中,递归实现
# A接口文档
# 返回体demo
{
"code": 0,
"msg": "error params",
"members":
[
{
"userId": 123
}
]
}
# 预期返回体
{
"code": int,
"msg": str,
"members":
[
{
"userId": 123
}
]
}
step2:实现方法框架
断言还是会应用unittest.TestCase下的断言方法,所以方法封装涉及到类继承
设计了形参
class AssertCommon(unittest.TestCase):
def func(self, expect, actual):
"""
json通用断言方法
:param expect: 定义预期返回体
:param actual: 实际返回的json
:return: 断言成功返回None,断言失败触发fail
"""
step3:实现字段存在的遍历
# 字段是否存在的校验
for key, value in expect.items():
self.assertIn(key, actual.keys())
step4:检测是否存在协议外的其他字段
# 校验是否存在多余的字段
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])
整体代码实现
import unittest
from common.caseOutput import error
class AssertCommon(unittest.TestCase):
# 状态码校验
def code_assert(self, expect, actual):
if expect != actual:
text = f'res code different, expect code: {expect}, actual code: {actual}.'
error('assert fail! ' + text)
self.fail(text)
def __assertEqual(self):
pass
def json_assert(self, expect, actual):
"""
json通用断言方法
:param expect: 定义预期返回体
:param actual: 实际返回的json
:return: 断言成功返回None,断言失败触发fail
"""
# 字段是否存在的校验
for key, value in expect.items():
self.assertIn(key, actual.keys())
# 校验是否存在多余的字段
self.assertEqual(len(expect.keys()), len(actual.keys()),
msg=f'response keys len different, response keys have: {list(actual.keys())}')
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]))}')
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):
# 是一个字典,我们本方法就是json字典的断言,递归调用方法本身
self.json_assert(value[i], actual[key][i])
else:
# 精准值校验
self.assertEqual(value[i], actual[key][i],
msg=f'list element {actual[key][i]} value different, actual response {actual[key]}')
else:
# 不是基础类型,也不是列表,是一个精准值,进行精准值校验
self.assertEqual(value, actual[key],
msg=f'{key} value error! actual value is {str(actual[key])}')
用例实现demo
import ...
@class_case_decoration
class GetCalendarNotesMajor(unittest.TestCase):
ac = AssertCommon()
br = BusinessRe()
env_config = YamlRead().env_config()
api_config = YamlRead().api_config()
host = env_config['host']
user_id = env_config['user_id']
wps_sid = env_config['wps_sid']
url = host + api_config['get_remind_note']['path']
def setUp(self) -> None:
"""实现数据清理方法"""
all_notes_clear(user_id=self.user_id, sid=self.wps_sid)
def test01_major(self):
"""主流程:查看2月日历便签"""
info('【前置步骤】请求上传/更新便签主体和内容接口,构建一条日历便签')
DataCreate().note_create(num=1, user_id=self.user_id, sid=self.wps_sid, remind_time=1739980800000)
info('【step】请求查看日历便签接口')
body = {
"remindStartTime": 1738339200000,
"remindEndTime": 1740758400000,
"startIndex": 0,
"rows": 1
}
res = self.br.post(url=self.url, sid=self.wps_sid, user_id=self.user_id, body=body)
expect = {
"responseTime": int,
"webNotes": [
{
"noteId": str,
"createTime": int,
"star": 0,
"remindTime": int,
"remindType": int,
"infoVersion": 1,
"infoUpdateTime": int,
"groupId": None,
"title": str,
"summary": str,
"thumbnail": None,
"contentVersion": 1,
"contentUpdateTime": int
}
]
}
self.ac.code_assert(200, res.status_code)
self.ac.json_assert(expect=expect, actual=res.json())
评论区