最近一直在忙着点点点,好长时间没更新新的文章了,今天写一下最近做功能测试过程中的一个思路。
需求背景:
有一系列的任务调2-3个外部接口获取数据后,入库到mysql数据库里面,然后会对外提供接口返回清洗后的数据。需要对这整个过程进行验证。
这中间可能会涉及到的点有:
1、外部接口的数据分别入库到mysql里面的数据是否正确,包括字段取值映射关系,数据总记录数等等。
2、数据源数据更新时,通过监听kafka消息及时更新mysql中的数据
3、测试一下对外提供的接口和kafka消息等。
测试过程中遇到的问题:
1、其实这些东西测起来不是很难,只是字段比较多 ,需要耗费一定的时间。加上最近的需求又有点多,以后类似这样的需求还有不少,再加上是新接手这块的需求,对一些字段映射关系啥的不是很清楚,对上游数据的改动和来源不熟悉,造数据覆盖不同场景需要耗费一定的时间
2、没开始测之前,以为接口对外输出应该比较好验证,结果等到我测的时候才发现,比我想象中稍微要麻烦一点点,本以为字段都是平铺返回的,这样我顶多处理一下字段映射关系,结果发现接口返回的时候还对不同的属性进行了分组,这样就导致到时候写代码的时候又会变得复杂很多。
接下来分享一下最后接口验证这块的一个写脚本的思路:
1、将mysql中的数据查出来,然后调对应的接口
2、按照接口返回的格式定义一套模板,将数据库里面的字段名和接口的字段名之间做一个映射关系转换
3、定义一个方法,传入mysql中的数据,替换调模板中的变量,然后按照接口的格式进行返回
4、用deepdiff库去对比从库中查出来的按照模板格式化后的数据和接口返回的数据进行对比。
注意:如果接口涉及到批量查询的时候,返回的大概率是一个对象的list,用deepdiff对比的时候,要注意列表中元素的顺序,最好自己将两边数据顺序都处理成一致的,避免插件对比的时候结果不符合预期。
下面附上替换模板中变量的参考代码,其中${xxx}格式表示xxx为变量名:
import pprint`` ``# 模板字典,包含嵌套字段``template = {` `"userName": "${user_name}",` `"userAge": "${user_age1}",` `"userInfo": {` `"phone": "${user_phone}",` `"email": "${user_email}"` `},` `"userInfoExt": {`` ` `"wxName": "${wx_name}"` `}``}`` ``# 实际数据字典``data = {` `"user_name": "小博",` `"wx_name": "小博测试成长之路",` `"user_age": 30,` `"user_age1": 1,` `"user_phone": "1234567890",` `"user_email": "example@example.com",` `"additional_field_1": "Additional Data 1",` `"additional_field_2": "Additional Data 2"``}`` `` ``def replace_variable(template, data):` `if isinstance(template, dict):` `result = {}` `for key, value in template.items():` `if isinstance(value, str):` `modified_value = ""` `i = 0` `while i < len(value):` `if value[i:i + 2] == "${" and "}" in value[i + 2:]:` `start = i` `end = value.index("}", i + 2)` `field_name = value[start + 2:end] # 提取字段名` `if field_name in data:` `modified_value += str(data[field_name])` `else:` `modified_value += value[start:end + 1] # 如果字段不存在,保留占位符不变` `i = end + 1` `else:` `modified_value += value[i]` `i += 1` `result[key] = modified_value` `elif isinstance(value, dict):` `# 如果值是字典,递归处理` `result[key] = replace_variable(value, data)` `else:` `result[key] = value` `return result` `else:` `return template`` `` ``# 调用递归函数进行替换``result = replace_variable(template, data)`` ``# 打印映射后的结果``pprint.pprint(result)`` ``
最后,考虑到接口取数据库的字段可能不止是字段名映射,可能还涉及到映射关系的转换或者计算之类,可以在定义一个函数去解析模板中符合某种格式的自定义函数,将函数返回值替换模板中的数据,下面代码仅供参考,格式$.xxx()为自定义函数,其中xxx为函数名。
import re``from pprint import pprint`` ``template = {'userAge': '$.int(1)',` `'userAge1': 'a$.int(1)',` `'userInfo': {'email': 'example@example.com', 'phone': '1234567890'},` `'userInfoExt': {'wxName': '小博测试成长之路'},` `'userName': '小博',` `"sex": "$.custom_func(1)"}`` `` ``def custom_func(value):` `return {"1": "男", "2": "女"}.get(value)`` `` ``def custom_function(value):` `try:` `# 使用正则表达式匹配函数调用` `match = re.match(r'(.*?)\$\.(.*?)\((.*?)\)(.*)', value)` `if match:` `text_before = match.group(1) if match.group(1) else ''` `function_name = match.group(2)` `function_args = match.group(3)` `text_after = match.group(4) if match.group(4) else ''`` ` `# 检查函数名是否是内置函数` `if function_name not in globals() or not callable(globals()[function_name]):` `# 如果是内置函数,使用 eval 调用自定义函数` `result = eval(f'{function_name}({function_args})')` `else:` `# 调用自定义函数` `result = globals()[function_name](function_args)`` ` `if text_before == "" and text_after == "":` `return result`` ` `return f'{text_before}{result}{text_after}'` `return value` `except Exception:` `return value`` `` ``def replace_funcs(template):` `if isinstance(template, dict):` `result = {}` `for key, value in template.items():` `result[key] = replace_funcs(value)` `return result` `elif isinstance(template, str):` `return custom_function(template)` `else:` `return template`` `` ``parsed_template = replace_funcs(template)``pprint(parsed_template)``
提供另一个demo:
import re``from pprint import pprint`` `` ``class SMTools:` `@staticmethod` `def custom_func1(value):` `return f"Custom Function 2 Result for {value}"`` ` `@staticmethod` `def replace_funcs(template):` `if isinstance(template, dict):` `for key, value in template.items():` `if isinstance(value, str):` `# 使用正则表达式匹配函数调用` `match = re.match(r'(.*?)\$\.(\w+)\((.*?)\)(.*)', value)` `if match:` `text_before = match.group(1) if match.group(1) else ''` `function_name = match.group(2)` `function_args = match.group(3)` `text_after = match.group(4) if match.group(4) else ''` `try:` `if hasattr(SMTools, function_name) and callable(getattr(SMTools, function_name)):` `# 检查是否是SMTools类中的自定义函数并调用它` `func = getattr(SMTools, function_name)` `result = func(function_args)` `value = f"{text_before}{result}{text_after}"` `else:` `# 处理非自定义函数` `result = eval(f'{function_name}({function_args})')` `if text_before == "" and text_after == "":` `value = result` `else:` `value = f"{text_before}{result}{text_after}"` `except Exception as e:` `# 处理异常情况` `value = f"{value} Error: {str(e)} "`` ` `template[key] = value` `elif isinstance(value, dict):` `# 递归处理嵌套字典` `template[key] = SMTools.replace_funcs(value)`` ` `return template # 返回处理后的template`` `` ``# 示例用法``template = {'userAge': '$.int(1)',` `'userAge1': 'a$.int(1)',` `'userInfo': {'email': 'example@example.com', 'phone': '1234567890'},` `'userInfoExt': {'wxName': '小博测试成长之路'},` `'userName': '小博',` `"sex": "$.custom_func1(1)"}`` ``SMTools.replace_funcs(template)`` ``pprint(template)``
此处的代码还不够完善,可能后续运行过程中模板转换可能会有不满足预期的情况,到时候调整对应的转换函数即可。重要的是解决问题的一个思路。
题外话
感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。
👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)
一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。
二、Python必备开发工具
工具都帮大家整理好了,安装就可直接上手!
三、最新Python学习笔记
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
四、Python视频合集
观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
五、实战案例
纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
六、面试宝典
简历模板
👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)
若有侵权,请联系删除