def test_main(self, case_number, case_title, path, is_token, method, parametric_key, file_var, file_path, parameters, dependent, data, expect): """ :param case_number: 用例编号 :param case_title: 用例标题 :param path: 接口路径 :param is_token: token操作:写入token/读取token/不携带token :param method: 请求方式:get/post/put/delete.... :param parametric_key: 入参关键字:params/data/json :param file_var: 接口中接受文件对象的参数名称 :param file_path: 文件路径,单文件实例:/Users/zy7y/PycharmProjects/apiAutoTest/test/__init__.py 多文件实例['/Users/zy7y/PycharmProjects/apiAutoTest/test/__init__.py','/Users/zy7y/PycharmProjects/apiAutoTest/test/test_api.py'] :param parameters: path参数(携带在url中的参数)依赖处理 users/:id(id携带在url中) 实例:{"case_001": '$.data.id'},解析 从用例编号为case_001的实际结果响应中提取data字典里面的id的内容(假设提取出来是500), 最后请求的路径将是host + users/500 :param dependent: data数据依赖,该接口需要上一个接口返回的响应中的某个字段及内容:实例{"case_001",["$.data.id","$.data.username"]} 解析: 从用例case_001的实际响应结果中提取到data下面的id,与username的值(假设id值为500,username为admin),那么提取的数据依赖内容将是{"id":500, "username":"******"} 纳闷最终请求的data 将是 {"id":500, "username":"******"} 与本身的data合并后的内容 :param data: 请求数据 :param expect:预期结果,最后与config/config.yaml下的response_reg->response提取出来的实际响应内容做对比,实现断言 :return: """ # 感谢:https://www.cnblogs.com/yoyoketang/p/13386145.html,提供动态添加标题的实例代码 # 动态添加标题 allure.dynamic.title(case_title) logger.debug(f'⬇️⬇️⬇️...执行用例编号:{case_number}...⬇️⬇️⬇️️') with allure.step("处理相关数据依赖,header"): data, header, parameters_path_url = treat_data.treating_data( is_token, parameters, dependent, data, save_response_dict) with allure.step("发送请求,取得响应结果的json串"): res = br.base_requests(method=method, url=base_url + path + parameters_path_url, parametric_key=parametric_key, file_var=file_var, file_path=file_path, data=data, header=header) with allure.step("将响应结果的内容写入实际响应字典中"): save_response_dict.save_actual_response(case_key=case_number, case_response=res) # 写token的接口必须是要正确无误能返回token的 if is_token == '写': with allure.step("从登录后的响应中提取token到header中"): treat_data.token_header[ 'Authorization'] = jsonpath.jsonpath(res, token_reg)[0] with allure.step("根据配置文件的提取响应规则提取实际数据"): really = jsonpath.jsonpath(res, res_reg)[0] with allure.step("处理读取出来的预期结果响应"): expect = json.loads(expect) with allure.step("预期结果与实际响应进行断言操作"): assert really == expect logger.info( f'完整的json响应: {res}\n需要校验的数据字典: {really} 预期校验的数据字典: {expect} \n测试结果: {really == expect}' ) logger.debug(f'⬆⬆⬆...用例编号:{case_number},执行完毕,日志查看...⬆⬆⬆\n\n️')
def get_data(self): """ :return: data_list - pytest参数化可用的数据 """ data_list = [] table = self.book.sheet_by_index(0) for norw in range(1, table.nrows): # 每行第4列 是否运行 if table.cell_value(norw, 3) != '否': # 每行第三列等于否将不读取内容 value = table.row_values(norw) value.pop(3) # 配合将每一行转换成元组存储,迎合 pytest的参数化操作,如不需要可以注释掉 value = tuple(value) value = tuple(value) logger.info(f'{value}') data_list.append(value) return data_list
def get_data(self): """ :return: data_list - pytest参数化可用的数据 """ data_list = [] table = self.book.sheet_by_index(0) for norw in range(1, table.nrows): # 每行第4列 是否运行 if table.cell_value(norw, 3) == '否': continue # 返回该行的所有单元格组成的数据 table.row_values(0) 0代表第1列 case_number = table.cell_value(norw, 0) case_title = table.cell_value(norw, 1) path = table.cell_value(norw, 2) is_token = table.cell_value(norw, 4) method = table.cell_value(norw, 5) # 入参关键字 parametric_key = table.cell_value(norw, 6) file_var = table.cell_value(norw, 7) file_path = table.cell_value(norw, 8) # 路径参数 parameters = table.cell_value(norw, 9) dependent = table.cell_value(norw, 10) data = table.cell_value(norw, 11) expect = table.cell_value(norw, 12) value = [ case_number, case_title, path, is_token, method, parametric_key, file_var, file_path, parameters, dependent, data, expect ] # 配合将每一行转换成元组存储,迎合 pytest的参数化操作,如不需要可以注释掉 value = tuple(value) value = tuple(value) logger.info(f'{value}') data_list.append(value) return data_list
def get_data(self): """ :return: data_list - pytest参数化可用的数据 """ data_list = [] table = self.book.sheet_by_index(0) #打开excel中第一个sheet页 返回表格 print(type(table)) print(table.nrows) # table.nrows获取table里面行的数量,从0开始算 for norw in range(1, table.nrows): #行下标0123,这里抛去第一行文字说明 # 每行第4列 是否运行 if table.cell_value(norw, 3) == '否': # 每行第三列 等于否 将不读取内容,列下标0123.... continue # 跳过这行,下一行第三个 value = table.row_values(norw) # 获取每行的所有值 是list value.pop(3) # 删除列表中下标为3,即上面判断是否执行的单元格数据 # 配合将每一行转换成元组存储,方便放入下面的大列表。迎合 pytest的参数化操作,如不需要可以注释掉 value = tuple(value) value = tuple(value) #列表转元组 logger.info(f'{value}') data_list.append(value) #一行数据的元组 再放入大列表 return data_list
#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'Wenbin Wu <*****@*****.**>' __credits__ = 'Python best' __date__ = 'Tue Apr 19 10:38:48 2011' __version__ = '0.1' from test import logger logger.info('test') class TEST(object): """docstring for TEST""" def __init__(self): super(TEST, self).__init__() l = [('a', 'aa'), ('b', list()), ('c', None)] for x, y in l: setattr(self, x, y) def p(self): self.b.append('aaa') print locals() print self.a print self.b if self.c is None: print 'aaaa' #t = TEST() #t.p()
def treating_data(self, is_token, parameters, dependent, data, save_response_dict): # 使用那个header if is_token == '': header = self.no_token_header else: header = self.token_header logger.info(f'处理依赖前data的数据:{data}') # 处理依赖数据data if dependent != '': if dependent.find('=') != -1: dependent_key = dependent.split('=')[0] dependent_value = dependent.split('=')[1] dependent_data = { dependent_key: save_response_dict.read_depend_data(dependent_value) } else: dependent_data = save_response_dict.read_depend_data(dependent) logger.debug(f'依赖数据解析获得的字典{dependent_data}') if data != '': data = json.loads(data) exists_key = False # 处理data与依赖中有相同key的问题, 目前之支持列表,字典,本地 列表形式调试通过,需要在定义时,data中该key定义成列表 # 实例{"id": [1],"user":{"username":"******"}} for k, v in data.items(): for dk, dv in dependent_data.items(): if k == dk: if isinstance(data[k], list): data[k].append(dv) if isinstance(data[k], dict): data[k].update(dv) exists_key = True if exists_key is False: # 合并组成一个新的data dependent_data.update(data) data = dependent_data logger.info(f'data有数据,依赖有数据时 {data}') else: # 赋值给data data = dependent_data logger.info(f'data无数据,依赖有数据时 {data}') else: if data == '': data = None logger.info(f'data无数据,依赖无数据时 {data}') else: data = json.loads(data) logger.info(f'data有数据,依赖无数据 {data}') # 处理路径参数Path的依赖 # 传进来的参数类似 {"case_002":"$.data.id"}/item/{"case_002":"$.meta.status"},进行列表拆分 path_list = parameters.split('/') # 获取列表长度迭代 for i in range(len(path_list)): # 按着 try: # 尝试序列化成dict: json.loads('2') 可以转换成2 path_dict = json.loads(path_list[i]) except JSONDecodeError as e: # 序列化失败此path_list[i]的值不变化 logger.error(f'无法转换字典,进入下一个检查,本轮值不发生变化:{path_list[i]},{e}') # 跳过进入下次循环 continue else: # 解析该字典,获得用例编号,表达式 logger.info(f'{path_dict}') # 处理json.loads('数字')正常序列化导致的AttributeError try: for k, v in path_dict.items(): try: # 尝试从对应的case实际响应提取某个字段内容 path_list[i] = jsonpath.jsonpath( save_response_dict.actual_response[k], v)[0] except TypeError as e: logger.error(f'无法提取,请检查响应字典中是否支持该表达式,{e}') except AttributeError as e: logger.error( f'类型错误:{type(path_list[i])},本此将不转换值 {path_list[i]},{e}' ) # 字典中存在有不是str的元素:使用map 转换成全字符串的列表 path_list = map(str, path_list) # 将字符串列表转换成字符:500/item/200 parameters_path_url = "/".join(path_list) logger.info(f'path路径参数解析依赖后的路径为{parameters_path_url}') return data, header, parameters_path_url
#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'Wenbin Wu <*****@*****.**>' __credits__ = 'Python best' __date__ = 'Tue Apr 19 10:38:48 2011' __version__ = '0.1' from test import logger logger.info('test') class TEST(object): """docstring for TEST""" def __init__(self): super(TEST, self).__init__() l = [('a', 'aa'), ('b',list()), ('c', None)] for x, y in l: setattr(self, x, y) def p(self): self.b.append('aaa') print locals() print self.a print self.b if self.c is None: print 'aaaa' #t = TEST() #t.p() def test_var_args(farg, **args): x = args.get('abc', 'xxx') print x
token_reg, res_reg = rc.read_response_reg() case_data_path = rc.read_file_path('case_data') report_data = rc.read_file_path('report_data') report_generate = rc.read_file_path('report_generate') log_path = rc.read_file_path('log_path') report_zip = rc.read_file_path('report_zip') email_setting = rc.read_email_setting() # 实例化存响应的对象 save_response_dict = SaveResponse() # 读取excel数据对象 data_list = ReadData(case_data_path).get_data() # 数据处理对象 treat_data = TreatingData() # 请求对象 br = BaseRequest() logger.info(f'配置文件/excel数据/对象实例化,等前置条件处理完毕\n\n') class TestApiAuto(object): # 启动方法 def run_test(self): import os, shutil if os.path.exists('../report') and os.path.exists('../log'): shutil.rmtree(path='../report') shutil.rmtree(path='../log') # 日志存取路径 logger.add(log_path, encoding='utf-8') pytest.main(args=[f'--alluredir={report_data}']) os.system( f'allure generate {report_data} -o {report_generate} --clean') logger.warning('报告已生成')
def treating_data(self, is_token, parameters, dependent, data, save_response_dict): #is_token 对应excel里面的token操作 空白 或者 读 写 #parameters 对应url路径path参数依赖的内容 #dependent 依赖数据的内容,元素表格中的jsonpath表达式 #data excel中 请求数据 内容,真正的data是请求+依赖 #save_response_dict 保存实际响应的字典对象 # 使用哪个header if is_token == '': header = self.no_token_header else: header = self.token_header logger.info(f'处理依赖前data的数据:{data}') # 处理依赖数据data (请求数据的依赖) if dependent != '': #dependent两种形式 #{"case_002": ["$.data.id"],"case_001":["$.meta.msg","$.meta.status"]} #key={"case_002": ["$.data.id"],"case_001":["$.meta.msg","$.meta.status"]} if dependent.find('=') != -1: #存在上面的key= 形式,就把key和后面的分开保存为字典 dependent_key = dependent.split('=')[0] dependent_value = dependent.split('=')[1] # dependent_data = {‘key’:{'id':1}} dependent_data = {dependent_key: save_response_dict.read_depend_data(dependent_value)} else: # {'id': 1} dependent_data是最终值 dependent_data = save_response_dict.read_depend_data(dependent) # 直接保存 logger.debug(f'依赖数据解析获得的字典{dependent_data}') if data != '': data = json.loads(data)#字符串转换为python对象,序列化 exists_key = False #不存在key就False,先默认为False,下面会具体判断有没有key # 处理data与依赖中有相同key的问题(原来请求就已经有name参数了,依赖里又从别的地方获取name参数), 目前支持列表,字典,本地 列表形式调试通过,需要在定义时,data中该key定义成列表 # 实例{"id": [1],"user":{"username":"******"}} for k, v in data.items(): #data excel中 请求数据栏 的内容,只是请求数据 for dk, dv in dependent_data.items(): #这是请求的依赖数据字典 if k == dk: #请求数据为{"id": [1], "user": {"username": "******"}},依赖数据为{"id": 500, "user": {"admin": "12345"}} #data = {"id": [1,500], "user": {{"username": "******"},{"admin": "12345"}} if isinstance(data[k], list): #有相同的key,并且key的类型为列表(这里是按照原来的请求数据判断),就直接添加 data[k].append(dv) if isinstance(data[k], dict): #如果key对应的是字典,就更新 data[k].update(dv) exists_key = True # 如果修改了就改为存在 if exists_key is False: #不存在同样key,直接合并 #合并组成一个新的data # dependent_data={'id':500} data={'username':'******'} # 最终data = {'id':500,'username':admin} dependent_data.update(data) data = dependent_data logger.info(f'data有数据,依赖有数据时 {data}') else: # data为空直接把依赖赋值给data data = dependent_data logger.info(f'data无数据,依赖有数据时 {data}') else: #依赖为空 if data == '': data = None logger.info(f'data无数据,依赖无数据时 {data}') else: data = json.loads(data) # 前面的依赖数据 已经在获取依赖数据的方法中python对象化了 logger.info(f'data有数据,依赖无数据 {data}') # 处理路径参数Path的依赖 (url路径的依赖) 总结:分开放,先把能提取对应转换的变过来,变不了的保留,最后一起map列表字符串化 # 传进来的参数类似 {"case_002":"$.data.id"}/item/{"case_002":"$.meta.status"},进行列表拆分 path_list = parameters.split('/') # {"case_002":"$.data.id"} item {"case_002":"$.meta.status"} # 获取列表长度迭代 for i in range(len(path_list)): # 按着 try: # 尝试序列化成dict: 中间的state或者数字2不能转换 path_dict = json.loads(path_list[i]) #字符串转换为python对象,Python对象包括:所有Python基本数据类型,列表,元组,字典,自己定义的类,等等等等 except JSONDecodeError as e: # 序列化失败此path_list[i]的值不变化 logger.error(f'无法转换字典,进入下一个检查,本轮值不发生变化:{path_list[i]},{e}') #json.loads('2'),转换不了先保存下面会再处理 # 跳过进入下次循环 continue else: # 解析该字典,获得用例编号,表达式 logger.info(f'{path_dict}') # 打印第一次转换成果,已经转换成功的放进来 # 处理json.loads('数字')正常序列化导致的AttributeError 为什么不直接map化呢???(先把能依赖的依赖了) try: for k, v in path_dict.items(): #{'case_002':'$.data.id'} case_002 . $.data.id try: # 尝试从对应的case实际响应提取某个字段内容{'id':500} , $.data.id 根据key从实际响应中提取value path_list[i] = jsonpath.jsonpath(save_response_dict.actual_response[k], v)[0] # 500 path_list[i]是最终地址 except TypeError as e: logger.error(f'无法提取,请检查响应字典中是否支持该表达式,{e}') # 类似找不到这种状态 except AttributeError as e: logger.error(f'类型错误:{type(path_list[i])},本此将不转换值 {path_list[i]},{e}') # 字典中存在有不是str的元素:使用map 转换成全字符串的列表 #path_list = [500,'item',200] path_list = map(str, path_list) #path_list = ['500','item','200'] # 将字符串列表转换成字符:500/item/200 parameters_path_url = "/".join(path_list) #相当于部分路径 logger.info(f'path路径参数解析依赖后的路径为{parameters_path_url}') return data, header, parameters_path_url