测试学习记录,仅供参考!
YAML 测试用例文件函数动态解析
为什么要做这个解析呢?
因为有的情况下,某个接口信息参数并不是固定的,有些情况需要获取别的接口信息的返回值;
例如:用户登录接口,其他用户需要登录接口的返回值作为它的一个入参,这种情况下就需要对它做一个解析,就是常说的热加载。
什么是热加载呢?
这里热加载就是指“应用程序在运行时去动态的来重新加载 被修改过来的代码,而不需要重新去启动整个应用程序” 。
1、在项目根目录 datas 目录文件路径下新建名称为adduser.yaml的文件,并输入以下内容(参考测试项目接口服务文档中的新增用户接口);
烦请按照格式内容输入,后续会详细介绍此文件格式约定标准(如若使用其他格式文件烦请自便);
- baseInfo: api_name: 用户新增 url: /dar/user/addUser method: post header: Content-Type: application/x-www-formurlencoded;charset=UTF-8 testCase: - case_name: 用户新增正常校验 data: username: testadduser password: tset6789890 role_id: 123456789 dates: '2023-12-31' phone: 13800000000 token: ${get_extract_data()}dates: '2023-12-31'日期格式这里使用字符串格式,容易解析,若不加字符串格式很容易报错的;token: ${get_extract_data()}这里token值不能直接写成固定的,因为每次从登录接口返回的 token 均是不一样的,所以不能写固定;后续可使用上下游传参方式来灵活地解决此类问题;
上下游传参
在项目根目录 unit_tools 软件包sendrequests.py中可查看到已经获取到了用户登录接口中的token值,而且也已经写入到了全局的extract.yaml文件里面;
在需要使用到token值时,可以直接在extract.yaml文件中读取即可;通过token: ${}方式去加载,通过token: ${get_extract_data()}这种方式去去解析这个extract.yaml文件,从而拿到extract.yaml文件中的token值;经过这一系列操作最终拿到全局文件extract.yaml中的token值,然后把这个token值再赋值给${get_extract_data()}就可以达到一个上下游的传参效果;
2、修改 unit_tools 软件包下sendrequests.py文件内容,把个Cookie值写入到文件里面去,直接调用写入write_yaml方法把获取到的Cookie值写入到全局配置extract.yaml文件里面去;
# 引入模块--导包 import requests from requests import utils import re from unit_tools.handle_data.yaml_handler import read_yaml, write_yaml # 使用类的方式去封装--定义一个类 SendRequests class SendRequests: # 使用 __init__ 创建构造函数 def __init__(self): # 占位符 pass # 类方法 加上 @classmethod 就是一个类方法 # 类方法不能去访问实例 初始化构造函数里面的属性,只能访问类的属性--需要把 self 改为 cls @classmethod # 定义一个方法 _text_encode --传一个接口返回的文本格式 res_text def _text_encode(cls, res_text): """ 处理接口返回值出现unicode编码时 例如:\\u767b :param res_text: :return: """ # 调用 re.search --去搜索这个文本(匹配正则表达式),第二个参数是接口的返回值res_text match = re.search(r"\\u[0-9a-fA-F]{4}", res_text) # 判断匹配的结果是否存在 if match: # 当匹配的结果存在时,把接口的返回值进行处理转换--先把它转码然后再解码 result = res_text.encode().decode('unicode_escape') else: # 若不存在,则直接把接口的返回值传过来 result = res_text # 最后return return result # 直接封装一个 send_request 方法--给它传一个可变数量的参数 **kwargs def send_request(self, **kwargs): # 创建一个会话--使用 requests.Session() 调用这个类并把它的结果返回出去 # 把创建的这个会话对象返回赋值给 session session = requests.Session() # 定义一个变量response并赋值为空 response = None # 这里最好再添加一个异常处理 try: # 直接session.调用request()把可变数量参数**kwargs给它传进来,返回赋值给 response response = session.request(**kwargs) # 通过调用requests这个模块--通过接口的返回值 response.cookies # 通过接口的返回对象获取cookies--定义变量set_cookie去接收 set_cookie = requests.utils.dict_from_cookiejar(response.cookies) # 判断是否有这个cookie if set_cookie: # 暂时注释--不做打印,后续再写入到文件中--写入到文件中之后,以后再次使用就比较方便 # print(f'获取到Cookies: {set_cookie}') # pass # 把Cookie写到文件里面去--调用写入write_yaml方法--把获取到的Cookie写入到全局配置extract.yaml文件中去 write_yaml({'Cookie': set_cookie}) # 调用self._text_encode方法--结果返回值response.text res = self._text_encode(response.text) print(res) # 连接异常 requests.exceptions.ConnectionError except requests.exceptions.ConnectionError: print("接口请求异常,可能是request的链接数过多或者速度过快导致程序报错!") # 请求异常 except requests.exceptions.RequestException as e: print(f'请求异常,请检查系统或数据是否正常!错误信息为:{e}') # 把发起接口请求的结果返回出去 return response # 再定义一个方法 execute_api_request--再来封装一个方法 # 这里传的参数比较多,一部分是data.yaml配置文件中的字段,最终根据request里面所需要的参数 # api_name, url, method, header--yaml配置文件;case_name--自定义; # cookie默认为空,file文件上传,这里也传空;最后再传可变数量参数 **kwargs def execute_api_request(self, api_name, url, method, header, case_name, cookie=None, file=None, **kwargs): """ 发起接口请求 :param api_name: 接口名称--后续打印日志会用到 :param url: 接口地址 :param method: 请求方法 :param header: 请求头 :param case_name: 测试用例名称--后续打印日志会用到 :param cookie: cookie :param file: 文件上传 :param kwargs: 未知数量的关键字参数 :return: """ # 直接使用self.调用上面封装的方法send_request() # 传参格式,这里的关键字务必跟request里面的关键字参数一致 # 关键字要接收的参数值是 上面定义的这个函数execute_api_request中的 参数, # 需要哪个传给哪个,一一对应即可--再把未知数量传参**kwargs给放进来 # timeout=10--接口超时设置10秒;verify=False--忽略HTTPS证书校验,默认verify=None # 最后把结果给返回出去并赋值给 response response = self.send_request(method=method, url=url, headers=header, cookies=cookie, files=file, timeout=10, verify=False, **kwargs) # 使用 return 直接返回结果 return response # 这里使用登录接口进行测试验证 if __name__ == '__main__': # 在这里引入或者最上面导入都行--一般都是在文件最上方导入 from unit_tools.handle_data.configParse import ConfigParser # 实例化对象ConfigParser()--调用get_host()方法--传入头参下面的下级参数值 host = ConfigParser().get_host('host') # 调用引用的模块里面的read_yaml()方法--相对路径 .././datas/login.yaml # 再拿到列表中的第一组元素--ps:不确定时可以打印出来详细查看 data = read_yaml('.././datas/login.yaml')[0] # 地址--服务器ip地址 + 接口地址 url = host + data['baseInfo']['url'] # 请求方法 同样依次取 key 值 method = data['baseInfo']['method'] # 请求头 同样依次取 key 值--一般都会需要请求头,除非特殊处理 header = data['baseInfo']['header'] # 入参--找到与baseInfo同级的testCase进行取值--testCase里面是一个列表--它里面只有一组数据--取对应值data req_data = data['testCase'][0]['data'] # 查看校验封装的方法能不能正常运行--SendRequests() 实例化这个类--把结果返回出去给自定义的变量对象 send send = SendRequests() # 这里传上面刚刚定义的变量 地址 url、请求方式 method、表头 header、入参 req_data # 这里请求头在接口中已特殊处理过,一般情况下都是需要正常传递的 res = send.execute_api_request(api_name=None, url=url, method=method, header=None, case_name=None, data=req_data) # 这里res的结果返回值response.text--它打印的是一个text属于字符串,字符串不能通过key值去取--所以需要先转换成一个json格式 res_json = res.json() # 转换成功之后即可获取它的token值 token = res_json['token'] # 获取它的userId user_id = res_json['userId'] # 调用write_yaml()方法写入文件 write_yaml({'token': token, 'userId': user_id})3、清空extract.yaml文件内容;
4、测试运行sendrequests.py文件(记得启动测试项目接口服务),执行成功后查看打印结果;
5、此时再去查看extract.yaml文件,可以看到在某些情况下,有的字段格式并不一样;
例如:有的情况是一个键的Cookie值下它里面又包含了一个字典格式;或者键是列表格式,列表里面又有多个字段值,又或者是某些情况下接口返回值需要提取多个的情况下,这些情况并不是固定的,烦请根据实际的项目接口返回值去判断;建议存到全局配置文件中;
如果这里需要获取Cookie键下面access_token_cookie键值对中的key值,应该怎么操作呢?
6、修改 unit_tools →handle_data 软件包下yaml_handler.py文件内容,专门写一个方法去获取全局配置文件extract.yaml的数据(具体详情可查看新定义方法get_extract_yaml中每一行代码注释);
# 导包 import yaml import os from configs.setting import FILE_PATH # 定义函数 read_yaml--传一个参数--要读取哪个yaml文件 def read_yaml(yaml_path): """ 读取yaml文件数据 :param yaml_path: :return: """ # 读取文件最好加上异常处理 try: # 使用open打开文件--使用这个后面可以不用手动关闭,它调用完之后会自动关闭文件句柄 # 打开哪个文件、权限、编码格式 with open(yaml_path, 'r', encoding='utf-8') as file: # 直接调用yaml.safe_load()加载文件file data = yaml.safe_load(file) # return返回 return data except UnicodeDecodeError: print(f"{yaml_path}文件编码格式错误,--尝试使用utf-8去解码YAML文件发送错误,请确保你的yaml文件是utf-8格式!") # yaml语法格式异常 yaml.YAMLError except yaml.YAMLError as e: print(f'Error:读取yaml文件失败,请检查格式 -{yaml_path},异常原因:{e}') except Exception as e: print(f"读取{yaml_path}文件时出现异常,原因:{e}") # 定义一个方法 write_yaml--(file_path 写入路径,value 写入一个数据) # 这个写入方法是专门把数据写到创建的extract.yaml中 def write_yaml(value): # 这里做自动化测试,一般只需要固定写入,传一个参数就行,按需自行选择 """ yaml文件数据写入 :param value:(dict)写入的数据,必须为字典格式 :return: """ # 往哪里写入数据--先引进模块 configs.setting --获取文件路径--不至于后面把路径写死 file_path = FILE_PATH['extract'] # 打印查看文件路径--看它有没有拿到 # print(file_path) # 判断这个文件 file_path 路径不存在时--引进 os 模块 if not os.path.exists(file_path): # 若在项目根目录下不存在extract.yaml文件,则新建一个 with open(file_path, 'w'): pass # 定义一个空的变量--因为作用域问题,若不在最外层定义空变量,file.close()则会显示file黄色下滑波浪线提示警告 file = None # 添加异常处理 try: # 使用open打开文件--文件名,权限 a 追加写入,编码格式--赋值给file file = open(file_path, 'a', encoding='utf-8') # 针对写入的 value 数据进行判断--写入的value是否为字典类型 if isinstance(value, dict): # 若value是字典类型--直接使用yaml.dump()调用yaml文件 # 把参数传给它 value,可以写入中文,按顺序去写入 write_data = yaml.dump(value, allow_unicode=True, sort_keys=False) # 调用文件file--使用write()写入--数据write_data file.write(write_data) else: # 若不是字典类型,则打印 print("写入的数据必须为字典类型!") except Exception as e: print(f"写入yaml文件出现异常,原因:{e}") finally: # 不管是否有异常,都执行关闭文件操作--因为前面使用的是 open--所以需要手动关闭文件句柄 file.close() # 定义一个清空文件方法 clear_yaml def clear_yaml(): """ 清空 extract.yaml 文件数据 :return: """ # 调用with open()--文件路径,权限 w ,编码格式 with open(FILE_PATH['extract'], 'w', encoding='utf-8') as f: # 调用一个方法 f.truncate()--调用这个方法就会清空yaml文件 f.truncate() # 定义方法get_extract_yaml--获取全局配置文件extract.yaml文件数据--传入两个参数 node_name 第一个节点,第二个参数默认为空,可以不传 def get_extract_yaml(node_name, sub_node_name=None): """ 用于获取extract.yaml文件的数据 :param node_name: 第一级key值 :param sub_node_name: 下级key值 :return: """ # 获取文件路径 file_path = FILE_PATH['extract'] # 添加异常 try: # 使用with open打开文件--文件路径,权限 r 读取,编码格式 with open(file_path, 'r', encoding='utf-8') as file: # 直接通过yaml.safe_load(file) extract_data = yaml.safe_load(file) # 如果第二个参数为空的话 if sub_node_name is None: # 直接return return extract_data[node_name] # yaml语法格式异常 yaml.YAMLError except yaml.YAMLError as e: print(f'Error:读取yaml文件失败,请检查格式 -{file_path},异常原因:{e}') # 再写一个总的异常 except Exception as e: print(f'Error:未知异常 - 原因:{e}') # 调试查看一下新写的 get_extract_yaml()方法有没有生效 if __name__ == '__main__': # 调用 get_extract_yaml 方法 res = get_extract_yaml("token") print(res)测试调试,可以发现能够先获取到对应的token键值;
7、再次修改 unit_tools →handle_data 软件包下yaml_handler.py文件内容,获取Cookie键下面access_token_cookie键值对中的key值;
# 导包 import yaml import os from configs.setting import FILE_PATH # 定义函数 read_yaml--传一个参数--要读取哪个yaml文件 def read_yaml(yaml_path): """ 读取yaml文件数据 :param yaml_path: :return: """ # 读取文件最好加上异常处理 try: # 使用open打开文件--使用这个后面可以不用手动关闭,它调用完之后会自动关闭文件句柄 # 打开哪个文件、权限、编码格式 with open(yaml_path, 'r', encoding='utf-8') as file: # 直接调用yaml.safe_load()加载文件file data = yaml.safe_load(file) # return返回 return data except UnicodeDecodeError: print(f"{yaml_path}文件编码格式错误,--尝试使用utf-8去解码YAML文件发送错误,请确保你的yaml文件是utf-8格式!") # yaml语法格式异常 yaml.YAMLError except yaml.YAMLError as e: print(f'Error:读取yaml文件失败,请检查格式 -{yaml_path},异常原因:{e}') except Exception as e: print(f"读取{yaml_path}文件时出现异常,原因:{e}") # 定义一个方法 write_yaml--(file_path 写入路径,value 写入一个数据) # 这个写入方法是专门把数据写到创建的extract.yaml中 def write_yaml(value): # 这里做自动化测试,一般只需要固定写入,传一个参数就行,按需自行选择 """ yaml文件数据写入 :param value:(dict)写入的数据,必须为字典格式 :return: """ # 往哪里写入数据--先引进模块 configs.setting --获取文件路径--不至于后面把路径写死 file_path = FILE_PATH['extract'] # 打印查看文件路径--看它有没有拿到 # print(file_path) # 判断这个文件 file_path 路径不存在时--引进 os 模块 if not os.path.exists(file_path): # 若在项目根目录下不存在extract.yaml文件,则新建一个 with open(file_path, 'w'): pass # 定义一个空的变量--因为作用域问题,若不在最外层定义空变量,file.close()则会显示file黄色下滑波浪线提示警告 file = None # 添加异常处理 try: # 使用open打开文件--文件名,权限 a 追加写入,编码格式--赋值给file file = open(file_path, 'a', encoding='utf-8') # 针对写入的 value 数据进行判断--写入的value是否为字典类型 if isinstance(value, dict): # 若value是字典类型--直接使用yaml.dump()调用yaml文件 # 把参数传给它 value,可以写入中文,按顺序去写入 write_data = yaml.dump(value, allow_unicode=True, sort_keys=False) # 调用文件file--使用write()写入--数据write_data file.write(write_data) else: # 若不是字典类型,则打印 print("写入的数据必须为字典类型!") except Exception as e: print(f"写入yaml文件出现异常,原因:{e}") finally: # 不管是否有异常,都执行关闭文件操作--因为前面使用的是 open--所以需要手动关闭文件句柄 file.close() # 定义一个清空文件方法 clear_yaml def clear_yaml(): """ 清空 extract.yaml 文件数据 :return: """ # 调用with open()--文件路径,权限 w ,编码格式 with open(FILE_PATH['extract'], 'w', encoding='utf-8') as f: # 调用一个方法 f.truncate()--调用这个方法就会清空yaml文件 f.truncate() # 定义方法get_extract_yaml--获取全局配置文件extract.yaml文件数据--传入两个参数 node_name 第一个节点,第二个参数默认为空,可以不传 def get_extract_yaml(node_name, sub_node_name=None): """ 用于获取extract.yaml文件的数据 :param node_name: 第一级key值 :param sub_node_name: 下级key值 :return: """ # 获取文件路径 file_path = FILE_PATH['extract'] # 添加异常 try: # 使用with open打开文件--文件路径,权限 r 读取,编码格式 with open(file_path, 'r', encoding='utf-8') as file: # 直接通过yaml.safe_load(file) extract_data = yaml.safe_load(file) # 如果第二个参数为空的话 if sub_node_name is None: # 直接return return extract_data[node_name] # 第二个参数不为空 else: # 通过最外层字典extract_data--要用get方法(防止没有相应key值报错) # 第一个参数node_name获取到的哪个key值--若没有这个key值,传一个{}空字典 # 再来一层get获取到它的下级节点 return extract_data.get(node_name, {}).get(sub_node_name, {}) # yaml语法格式异常 yaml.YAMLError except yaml.YAMLError as e: print(f'Error:读取yaml文件失败,请检查格式 -{file_path},异常原因:{e}') # 再写一个总的异常 except Exception as e: print(f'Error:未知异常 - 原因:{e}') # 调试查看一下新写的 get_extract_yaml()方法有没有生效 if __name__ == '__main__': # 调用 get_extract_yaml 方法 res = get_extract_yaml("Cookie", 'access_token_cookie') print(res)实现 get_extract_data() 方法
8、在项目根目录 unit_tools 软件包下新建一个名称为debugtalk.py的 Python 文件,专门用来对测试接口进行一些要实现的方法都可以写到这个文件里面来;
# 导包 import random # random -- 随机数 import re # re -- 正则表达式 from unit_tools.handle_data.yaml_handler import get_extract_yaml # 定义一个类 DebugTalk class DebugTalk: # 定义一个方法 get_extract_data提取全局配置文件extract.yaml变量--第一个参数 node_name;第二个参数这里默认为空,可以传也可以不传 def get_extract_data(self, node_name, out_format=None): """ 获取全局yaml文件extract.yaml数据,首先判断out_format是否为数字类型,如果不是就获取下一个节点的value :param node_name: 全局文件extract.yaml文件中的key--例如:Cookie、access_token_cookie、token :param out_format: str类型,0:随机去读取;-1:读取全部数据,返回字符串格式;-2:读取全部,返回列表格式;其他值的就按顺序读取 :return: """ # 导包引入模块之后--直接调用方法get_extract_yaml()--需要传两个参数,暂时先传一个参数 node_name--返回值赋值给变量data data = get_extract_yaml(node_name) # 若是数值类型的--判断第二个参数不等于空 并且 --加一个布尔值--正则表达式,判断传入的第二个参数是数值的形式还是一个字母参数的形式 if out_format is not None and bool(re.compile(r'^[+-]?\d+$').match(str(out_format))): # 转换格式成int类型--返回赋值给变量out_format out_format = int(out_format) # 定义一个字典--或者使用if else data_value = { # 若传的这个参数out_format存在--通过self.seq_read(data, out_format)调用 out_format: self.seq_read(data, out_format), # 若第二参数传入的是 0 -- 通过随机函数-- 随机去读取 0: random.choice(data), # 传入的是 -1 -- 去读取全部,返回字符串格式--列表怎么转字符串--通过 ','.join(data) 逗号分隔.join() 组成新的字符串 -1: ','.join(data), # 若传入的是 -2 --读取全部,返回列表格式 -2: ','.join(data).split(',') } # 通过data_value取值--取第二个值--返回出去赋值给变量data data = data_value[out_format] else: # 若第二个参数传入的不是数字--调用get_extract_yaml方法--把这两个node_name, out_format参数给它传进来 data = get_extract_yaml(node_name, out_format) # return--把data值返回出去 return data # 定义一个方法 seq_read--参入两个参数,第一个data,第二个随机数randoms def seq_read(self, data, randoms): """ 获取extract.yaml,第二个参数不为 0、-1、-2的情况下 """ # 直接判断这个参数randoms不是0、-1、-2 if randoms not in [0, -1, -2]: # 直接返回一个>![]()
处理接口返回值是多个值的情况
9、修改项目根目录下extract.yaml文件内容,假设接口返回值是多个情况下,例如:键值是列表格式,列表里面又有多个字段值;
Cookie: access_token_cookie: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc3ODM4ODYxNSwianRpIjoiMGYyZGZhNjMtMTU0Yi00MGI3LWIwY2ItODI4MWQ1NTM5OWM2IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImV4YW1wbGVfdXNlciIsIm5iZiI6MTc3ODM4ODYxNSwiZXhwIjoxNzc4Mzg5NTE1fQ.qqqXQ124i29OvFz3Oz21JBGjxLdoXSjrnCM-t6PXBLo token: C99BCb2e7B25a9c35bFf124A07eAc userId: '7789876390143403781' goodsId: - '11111111111' - '22222222222' - '33333333333' - '44444444444'
![]()
如果需要获取goodsId下里面对应的值,可自行测试调试查看;
# 测试调试查看 if __name__ == '__main__': # 实例化类 DebugTalk() debug = DebugTalk() # 通过对象取调用方法--第一个参数是goodsId # 第二个参数(0:随机读取,1~4:按顺序依次读取,-1:读取全部,-2:读取全部,返回列表格式,-3是第一个) # 根据实际情况其他超出范围会报“索引错误:列表索引超出范围 ” IndexError: list index out of range res = debug.get_extract_data('goodsId','00000') print(f'0是随机读取: {res}') print(f'-1是读取全部: {debug.get_extract_data('goodsId','-01')}') print(f'-2是读取全部并返回列表格式: {debug.get_extract_data('goodsId','-02')}')
![]()
未完待续。。。