Python自动化测试用例:如何优雅的完成Json格式数据断言

目录

前言

直接使用

优化

封装

小结

进阶

总结

 资料获取方法


前言

记录Json断言在工作中的应用进阶。

直接使用

很早以前写过一篇博客,记录当时获取一个多级json中指定key的数据:

#! /usr/bin/python
# coding:utf-8 
""" 
@author:Bingo.he 
@file: get_target_value.py 
@time: 2017/12/22 
"""
def get_target_value(key, dic, tmp_list):""":param key: 目标key值:param dic: JSON数据:param tmp_list: 用于存储获取的数据:return: list"""if not isinstance(dic, dict) or not isinstance(tmp_list, list):  # 对传入数据进行格式校验return 'argv[1] not an dict or argv[-1] not an list 'if key in dic.keys():tmp_list.append(dic[key])  # 传入数据存在则存入tmp_listfor value in dic.values():  # 传入数据不符合则对其value值进行遍历if isinstance(value, dict):get_target_value(key, value, tmp_list)  # 传入数据的value值是字典,则直接调用自身elif isinstance(value, (list, tuple)):_get_value(key, value, tmp_list)  # 传入数据的value值是列表或者元组,则调用_get_valuereturn tmp_listdef _get_value(key, val, tmp_list):for val_ in val:if isinstance(val_, dict):  get_target_value(key, val_, tmp_list)  # 传入数据的value值是字典,则调用get_target_valueelif isinstance(val_, (list, tuple)):_get_value(key, val_, tmp_list)   # 传入数据的value值是列表或者元组,则调用自身

优化

后来在写用例生成过程中,发现重复的断言描述信息较多,大多数数据返回其实都是标准的json,所以将整个返回的json放到断言数据中:

continue_run_flag = Truedef assert_json(base, juge, contain=(), reg=()):# 返回值,不符合预期时会设置为Falseflag = Truefor key, value in base.items():# 不进行断言的数据不进一步处理if key not in juge:continueif key in COMMON_RE_CHECK:if not re.match(COMMON_RE_CHECK[key], base[key]):flag = Falselogger.error("字段[{}]使用通用的正则匹配,不符合预期:预期正则匹配【{}】== 【{}】".format(key, COMMON_RE_CHECK[key], juge[key]))else:logger.warning("字段【{}】使用通用字段的正则匹配, 符合预期".format(key))continueif key in contain:if str(value) not in juge[key]:flag = Falselogger.error("字段[{}]不符合预期:预期【{}】包含 【{}】".format(key, juge[key], value))continuelogger.info("字段[{}]符合预期:预期[{}] 包含 [{}]".format(key, juge[key], value))continueif key in reg:if not re.match(juge[key], value):flag = Falselogger.error("字段[{}]不符合预期:预期正则匹配【{}】== 【{}】".format(key, value, juge[key]))continuelogger.info("字段[{}]断言成功:预期[{}]== 实际[{}]".format(key, value, juge[key]))continueif isinstance(value, str) or isinstance(value, int):if juge[key] != value:logger.error("字段[{}]不符合预期:预期【{}】!= 实际【{}】".format(key, value, juge[key]))flag = Falsecontinueelif isinstance(value, dict):assert_json(value, juge[key], contain=contain, reg=reg)elif isinstance(value, list):for i, v in enumerate(value):if isinstance(value, str) or isinstance(value, int):if v != juge[key][i]:logger.error("字段[{}]不符合预期:预期【{}】!= 实际【{}】".format(key, value, juge[key]))else:logger.info("字段[{}]断言成功:预期[{}]== 实际[{}]".format(key, value, juge[key]))elif isinstance(value, dict):assert_json(value[i], juge[key][i], contain=contain, reg=reg)else:assert_json(value[i], juge[key][i], contain=contain, reg=reg)else:passlogger.info("字段[{}]符合预期: 预期[{}]== 实际[{}]".format(key, value, juge[key]))# 失败是否继续执行,默认为TRUEif not continue_run_flag:assert flagreturn flag

调用:

rsp = requests.get('http://localhost:8800/get', params={"name": "bingo", "age": 18}).json()
assert_json(rsp, {"args": {"name": "bingo"},"headers": {"Accept": "*/*","Accept-Encoding": "gzip, deflate","Cache-Control": "max-age=259200","Host": "httpbin.org","User-Agent": "python-requests/2.27.1","X-Amzn-Trace-Id": r"Root=\d-\w{8}-\w{24}"},"req_param": [{"name": "bingo","age": "18"}],"origin": r"","url": "http://httpbin.org/get?name=bingo"
},
contain=(), reg=("X-Amzn-Trace-Id", "origin",))

日志效果:

2022-05-05 14:25:49.967 | INFO     | __main__:assert_json:173 - 字段[name]符合预期: 预期[bingo]== 实际[bingo]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[args]符合预期: 预期[{'name': 'bingo'}]== 实际[{'name': 'bingo'}]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Accept]符合预期: 预期[*/*]== 实际[*/*]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Accept-Encoding]符合预期: 预期[gzip, deflate]== 实际[gzip, deflate]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Cache-Control]符合预期: 预期[max-age=259200]== 实际[max-age=259200]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Host]符合预期: 预期[httpbin.org]== 实际[httpbin.org]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[User-Agent]符合预期: 预期[python-requests/2.27.1]== 实际[python-requests/2.27.1]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:149 - 字段[X-Amzn-Trace-Id]断言成功:预期[Root=1-62734553-430db0707e1a3656043cd165]== 实际[Root=\d-\w{8}-\w{24}]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[headers]符合预期: 预期[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=1-62734553-430db0707e1a3656043cd165'}]== 实际[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=\\d-\\w{8}-\\w{24}'}]
2022-05-05 14:25:49.969 | WARNING  | __main__:assert_json:133 - 字段【origin】使用通用字段的正则匹配, 符合预期
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[age]符合预期: 预期[18]== 实际[18]
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[name]符合预期: 预期[bingo]== 实际[bingo]
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[req_param]符合预期: 预期[[{'age': '18', 'name': 'bingo'}]]== 实际[[{'name': 'bingo', 'age': '18'}]]
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[url]符合预期: 预期[http://httpbin.org/get?name=bingo]== 实际[http://httpbin.org/get?name=bingo]

封装

将方法简单封装到调用类中:

class HttpBin:def __init__(self):self.continue_run_flag = True  # 失败是否继续执行self.base_url = 'http://localhost:8800'self.base_param = {"local_class": self.__class__.__name__}def get(self, param):path = "/get"param.update(self.base_param)self.rsp = requests.get(self.base_url + path, params=param)self.ans = self.rsp.json()logger.info(json.dumps(self.rsp.json(), indent=4))return selfdef set(self, param):path = "/set"param.update(self.base_param)self.rsp = requests.get(self.base_url + path, params=param)self.ans = self.rsp.json()logger.info(json.dumps(self.rsp.json(), indent=4))return selfdef assert_statusCode(self, result_code):""":param result_code: 包含关系断言:return: bool <= self.rsp.resultinfo"""# 返回值,不符合预期时会设置为Falseflag = Trueif int(result_code) != self.rsp.status_code:logger.error(f"返回状态码[result_code]不符合预期:预期【{result_code}】!= 实际【{self.rsp.status_code}】")flag = Falseelse:logger.info(f"返回状态码[result_code]符合预期:预期【{result_code}】!= 实际【{self.rsp.status_code}】")if not self.continue_run_flag:assert flagreturn selfdef assert_json_body(self, base, juge, contain=(), reg=()):...

用例调用

# 如果仅仅断言状态码
HttpBin().get({"name": "bingo", "age": 18}).assert_statusCode(200)# 级连调用多个接口,使用同一个初始化数据
HttpBin().get({"name": "bingo", "age": 18}).assert_statusCode(200).\set({"name": "he", "age": 18}).assert_statusCode(200).assert_json_body(juge={"args": {"name": "bingo"},"headers": {"Accept": "*/*","Accept-Encoding": "gzip, deflate","Cache-Control": "max-age=259200","Host": "httpbin.org","User-Agent": "python-requests/2.27.1","X-Amzn-Trace-Id": r"Root=\d-\w{8}-\w{24}"},"req_param": [{"name": "he","age": "18"}],"origin": r"","url": "http://httpbin.org/set?name=bingo"}, contain=(), reg=("X-Amzn-Trace-Id", "origin",))

运行效果:

2022-05-05 19:39:36.951 | INFO     | __main__:assert_statusCode:53 - 返回状态码[result_code]符合预期:预期【200】!= 实际【200】
2022-05-05 19:39:36.951 | INFO     | __main__:assert_json_body:117 - 字段[name]符合预期: 预期[bingo]== 实际[bingo]
2022-05-05 19:39:36.951 | INFO     | __main__:assert_json_body:117 - 字段[args]符合预期: 预期[{'name': 'bingo'}]== 实际[{'name': 'bingo'}]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Accept]符合预期: 预期[*/*]== 实际[*/*]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Accept-Encoding]符合预期: 预期[gzip, deflate]== 实际[gzip, deflate]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Cache-Control]符合预期: 预期[max-age=259200]== 实际[max-age=259200]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Host]符合预期: 预期[httpbin.org]== 实际[httpbin.org]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[User-Agent]符合预期: 预期[python-requests/2.27.1]== 实际[python-requests/2.27.1]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:93 - 字段[X-Amzn-Trace-Id]断言成功:预期[Root=1-62734553-430db0707e1a3656043cd165]== 实际[Root=\d-\w{8}-\w{24}]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[headers]符合预期: 预期[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=1-62734553-430db0707e1a3656043cd165'}]== 实际[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=\\d-\\w{8}-\\w{24}'}]
2022-05-05 19:39:36.953 | WARNING  | __main__:assert_json_body:77 - 字段【origin】使用通用字段的正则匹配, 符合预期
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[age]符合预期: 预期[18]== 实际[18]
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[name]符合预期: 预期[he]== 实际[he]
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[req_param]符合预期: 预期[[{'age': '18', 'local_class': 'HttpBin', 'name': 'he'}]]== 实际[[{'name': 'he', 'age': '18'}]]
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[url]符合预期: 预期[http://httpbin.org/set?name=bingo]== 实际[http://httpbin.org/set?name=bingo]

小结

  • 可以作为独立函数使用,也可以和所有的接口一起封装,灵活度、复用度高
  • 用例极简且逻辑清晰
  • 支持断言失败,用例继续执行(开关控制),方便一次性发现所有的差异
  • 极易编写的统一断言(从日志获取轻松获取
    • 用例运行日志,断言数据字段清晰明确
    • 可统一化处理新版本修改字段,无需修改每个用例
    • 支持正则匹配
    • 支持**包含匹配 **
    • 支持错误码直接断言
  • 集中初始化原始常用数据,不同业务适配简单,重写初始化方法即可

进阶

上面的方法虽然使用日志的方法记录了所有的差异,但是面对大json对比的时候,很难直接标记出具体的差异位置。在做现网引流对比测试的时候就出现了这样的需求,从现网拉取的账户数据可能存在几百个子账户,每个子账户有20多个属性字段,怎么准确标记他们在新旧系统的写操作后不一致的情况成为了一个小卡点。

话不多说,思路:利用列表可变特性和生成器关键字yield特性递归分解json,生成一个固定的数组,最终比较数组中的数据

代码:

def recurse(d, prefix=None, sep='.'):if prefix is None:prefix = []for key, value in d.items():if isinstance(value, dict):yield from recurse(value, prefix + [key])elif isinstance(value, list):for i, v in enumerate(value):if isinstance(v, dict):yield from recurse(v, prefix + [key, f"${i}"])# 兼容 包含数字的类型elif isinstance(v, int) or isinstance(v, str):yield sep.join(prefix + [key, str(value)])  # 会嵌套上valueelse:# print(key)yield sep.join(prefix + [key, str(value)])  # 会嵌套上value

效果:

print(json.dumps(list(recurse({"args": {"name": "bingo"},"headers": {"Accept": "*/*","Accept-Encoding": "gzip, deflate","Cache-Control": "max-age=259200","Host": "httpbin.org","User-Agent": "python-requests/2.27.1","X-Amzn-Trace-Id": r"Root=\d-\w{8}-\w{24}"},"req_param": [{"name": "bingo","age": "18"},{"name": "he","age": "19"},{"name": "detector","age": "20"}],"origin": r"","url": "http://httpbin.org/set?name=bingo"})), indent=4))
# 输出
["args.name.bingo","headers.Accept.*/*","headers.Accept-Encoding.gzip, deflate","headers.Cache-Control.max-age=259200","headers.Host.httpbin.org","headers.User-Agent.python-requests/2.27.1","headers.X-Amzn-Trace-Id.Root=\\d-\\w{8}-\\w{24}","req_param.$0.name.bingo","req_param.$0.age.18","req_param.$1.name.he","req_param.$1.age.19","req_param.$2.name.detector","req_param.$2.age.20","origin.","url.http://httpbin.org/set?name=bingo"
]

总结

项目实践中总会遇到这样那样的需求,每个方法都有适用的场景,直接高效解决问题是第一要务。

  • json查找数据
  • json数据用例封装对比
  • json数据转化

 资料获取方法

【留言777】

各位想获取源码等教程资料的朋友请点赞 + 评论 + 收藏,三连!

三连之后我会在评论区挨个私信发给你们~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/81642.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

399. 除法求值

题目描述&#xff1a; 主要思路&#xff1a; 本题主要利用并查集的思想&#xff0c;重点是要弄明白分子和分母的指向关系以及一系列的值的变化规则。 查询时如果两个数字不在一个集合里那么结果就为-1. class Solution { public:unordered_map<string,string> f;unorde…

MongoDB创建用户 、数据库、索引等基础操作

MongoDB的权限认证是相对来说比较复杂的&#xff0c;不同的库创建后需要创建用户来管理。 本机中的MongoDB是docker 启动的&#xff0c;所以先进入docker的镜像中 docker exec -it mongodb bash 这样就进入到了镜像MongoDB中&#xff0c;然后输入命令连接MongoDB数据库 注…

【Spring】实现FactoryBean接口

FactoryBean FactoryBean是一个接口&#xff0c;需要创建一个类来实现该接口&#xff0c;该接口中有三个方法&#xff0c;通过重写其中的两个方法&#xff0c;获得一个对象&#xff0c;三个方法分别是&#xff1a; 1.getObject():通过一个对象交给IOC容器管理2.getObjectType(…

亿欧智库:2023中国宠物行业新趋势洞察报告(附下载)

关于报告的所有内容&#xff0c;公众【营销人星球】获取下载查看 核心观点 户外赛道本质上迎合了全球共性需求的增长&#xff0c;从养宠意愿的转化到养宠生活的需求&#xff0c;多层次的需求推动行业发展新趋势 从需求端进行分析&#xff0c;可以将养宠意愿的转化分为三个层…

解决:Springboot视频接口报大量的ClientAbortException找不到原因

浏览器有自己的缓冲策略&#xff0c;比如视频接口吐出了100MB的视频数据&#xff0c;浏览器可不会全部拿走&#xff0c;而是按需去拿&#xff0c; 举个例子&#xff0c;浏览器拿的视频数据够看半分钟的&#xff0c;就停止读取数据了&#xff0c;但是http连接并未断开&#xff…

安装ubuntu 18.04 系统(1)——制作系统安装U盘

https://rufus.ie/zh/ 下载该软件&#xff0c;准备制作启动盘下载自己想要的镜像&#xff0c;http://mirrors.163.com/ubuntu-releases/18.04/&#xff0c; 我选择的是ubuntu-18.04.6-live-server-amd64.iso 因为&#xff0c;科研写程序使用&#xff0c;不需要桌面版本。开始制…

pytorch Stream 多流处理

CUD Stream https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-language-extensions 中指出在kenel的调用函数中最后一个可选参数表示该核函数处在哪个流之中。 - 参数Dg用于定义整个grid的维度和尺寸&#xff0c;即一个grid有多少个block。为dim3类型。…

深入探索C++模板:从基础到高级应用

目录 一、 泛型编程 1.1 为什么需要泛型编程&#xff1f; 二、模板 2.1 概念 2.2 函数模板 2.2.1 概念 2.2.2 语法 2.2.3 示例 2.2.4 模板实例化 隐式实例化 显示实例化 2.2.5 模板参数的匹配原则 2.3 类模板 2.3.1 概念 2.3.2 语法 2.3.3 示例 2.3.4 注意事项…

Html页面连线工具

在项目中遇了一个需要连线配置的功能。该功能引用了 bootstrap、layui 、svg-line等插件 下载链接 lixhttps://download.csdn.net/download/dongyan3595/88168121

用Python获取链家二手房房源数据,做可视化图分析数据

前言 数据采集的步骤是固定: 发送请求, 模拟浏览器对于url地址发送请求获取数据, 获取网页数据内容 --> 请求那个链接地址, 返回服务器响应数据解析数据, 提取我们需要的数据内容保存数据, 保存本地文件 所需模块 win R 输入cmd 输入安装命令 pip install 模块名 (如果你…

机器学习竞赛、CV/NLP 竞赛入门没人带解决办法

我的第一个AI竞赛是在Datawhale夏令营的帮助下参加的。 一周左右的夏令营&#xff0c;让我从环境都配不好的小白到对pytorch基本入门。 夏令营的氛围督促我赶紧学习&#xff0c;赶紧进步&#xff1b;夏令营群里的大佬推荐的入门视频让我pytorch入门。 了解datawhale是通过b站…

github 无语的问题,Host does not existfatal: Could not read from remote repository.

Unable to open connection: Host does not existfatal: Could not read from remote repository. image.png image.png image.png Please make sure you have the correct access rights and the repository exists. 如果github desktop和git pull 和git clone全部都出问题了&…

pytest常用执行参数详解

1. 查看pytest所有可用参数 我们可以通过pytest -h来查看所有可用参数。 从图中可以看出&#xff0c;pytest的参数有很多&#xff0c;下面是归纳一些常用的参数&#xff1a; -s&#xff1a;输出调试信息&#xff0c;包括print打印的信息。 -v&#xff1a;显示更详细的信息。 …

手眼标定眼在手上

1、为什么要用手眼标定&#xff08;在贴片机上定位已调通&#xff09; 参考手眼标定特别是眼在手上在网上的文章很多&#xff0c;但很多在实际中调试不通。在定位时候&#xff0c;往往希望相机能返回的是机械的世界坐标&#xff0c;而不是相机的的图像坐标。从而间接计算出相机…

【移动机器人运动规划】03 —— 基于运动学、动力学约束的路径规划(一)

文章目录 前言相关代码整理:相关文章&#xff1a; 介绍什么是kinodynamic&#xff1f;为什么需要kinodynamic&#xff1f;模型示例unicycle model&#xff08;独轮车模型&#xff09;differential model&#xff08;两轮差速模型&#xff09;Simplified car model (简化车辆模型…

机器学习参数调优

手动调参 分析影响模型的参数&#xff0c;设计步长进行交叉验证 我们以随机森林为例&#xff1a; 本文将使用sklearn自带的乳腺癌数据集&#xff0c;建立随机森林&#xff0c;并基于泛化误差&#xff08;Genelization Error&#xff09;与模型复杂度的关系来对模型进行调参&…

TOPIAM 社区版 1.0.0 发布,开源 IAM/IDaaS 企业身份管理平台

文章目录 产品概述系统架构功能列表管理端门户端 技术架构后续规划相关地址 ​Hi&#xff0c;亲爱的朋友们&#xff0c;今天是传统 24 节气中的立秋&#xff0c;秋天是禾谷成熟、收获的季节。经过长时间优化和迭代&#xff0c;TOPIAM 企业身份管控平台也迎来了当下的成长和收获…

PWNlab靶机渗透

安装靶机 下载地址&#xff1a;https://www/vulnhub.com/entry/pwnlab-init,158/ 信息收集&#xff1a; 收集靶机ip地址&#xff0c;由于搭建在本地使用kali自带命令 arp-scan -l nmap 扫描端口&#xff0c;服务 nmap -sV -p 1-65535 -A 靶机ip地址 漏洞探测 访问80端口地…

kubernetes 集群命令行工具 kubectl

1、kubectl 概述 kubectl是一种命令行工具&#xff0c;用于管理Kubernetes集群和与其相关的资源。通过kubectl&#xff0c;您可以查看和管理Pod、Deployment、Service、Volume、ConfigMap等资源&#xff0c;也可以创建、删除和更新它们。 kubectl还提供了许多其他功能&#x…

监控Kafka的关键指标

Kafka 架构 上面绿色部分 PRODUCER&#xff08;生产者&#xff09;和下面紫色部分 CONSUMER&#xff08;消费者&#xff09;是业务程序&#xff0c;通常由研发人员埋点解决监控问题&#xff0c;如果是 Java 客户端也会暴露 JMX 指标。组件运维监控层面着重关注蓝色部分的 BROKE…