实现思路

1.抓取api信息(目前公司用的swagger),uri、method、params、response,解析完成后写入excle

2.读取抓取完毕的api信息,处理为allpairs所需要的ordereddict

3.调用allpairs工具生成测试用例

4.解析allpairs生成的测试用例(输出为字符串),并处理为dict

5.处理完毕后写入excel

后期优化

1.根据接口响应实现自动断言

2.增加其他接口平台的api抓取(openapi、eolink等)

3.增加其他自动化生成用例的方法(有效、无效等价类,流量回放等)

4.集成到平台

  1. # _*_ coding: UTF-8 _*_
  2. """
  3. @project -> file : city-test -> rr
  4. @Author : qinmin.vendor
  5. @Date : 2023/1/29 19:44
  6. @Desc : 自动生成接口测试用例:支持正交实验,等价类,边界值
  7. """
  8. import copy
  9. import json
  10. import os
  11. import random
  12. import re
  13. import sys
  14. from collections import OrderedDict
  15. from allpairspy import AllPairs
  16. from utils.operation_datas import operationExcle
  17. import math
  18. from utils.time_utils import timeUtil
  19. from utils.wrapper_util import exec_time_wrapper
  20. from inspect import isfunction
  21. from faker import Faker
  22. '''faker使用教程:https://blog.csdn.net/qq_42412061/article/details/122997802'''
  23. class autoGenrateApiCaseAllpairspy(object):
  24. time_util = timeUtil()
  25. fake = Faker(locale="zh_cn") # 指定语言为中文
  26. CONST_MAX_STRING_LENGTH = 100
  27. CONST_MIN_STRING_LENGTH = 1
  28. # 生成字符串相关的数据
  29. # special_chars是否包含特殊字符;digits是否包含数字;upper_case是否包含大写字母,lower_case是否包含小写字母
  30. special_character = fake.password(length=8, special_chars=True, digits=False, upper_case=False,
  31. lower_case=False) # 生成随机特殊字符
  32. random_str = fake.paragraph() # 生成随机字符
  33. random_str_max = random_str * math.ceil(CONST_MAX_STRING_LENGTH / len(random_str))
  34. phone_number = fake.phone_number() # 生成随机电话号码
  35. # 生成时间相关的数据
  36. random_date = fake.date(pattern="%Y-%m-%d", end_datetime=None) # 生成随机日期
  37. random_start_datetime = time_util.adjust_time(num=-30)
  38. random_end_datetime = time_util.adjust_time(num=0)
  39. random_start_timestamp_millisecond = time_util.adjust_time(num=-30, is_timestamp=True, millisecond=True)
  40. random_end_timestamp_millisecond = time_util.adjust_time(num=0, is_timestamp=True, millisecond=True)
  41. random_start_timestamp = time_util.adjust_time(num=-30, is_timestamp=True, millisecond=False)
  42. random_end_timestamp = time_util.adjust_time(num=0, is_timestamp=True, millisecond=False)
  43. # 生成int与float相关的数据
  44. random_int = random.randint(1, 100)
  45. random_float = round(random.random(), 6) + random_int # 生成随机字符
  46. random_int_max = str(random_int) * math.ceil(CONST_MAX_STRING_LENGTH / len(str(random_int)))
  47. random_float_max = str(str(random_float) *
  48. math.ceil(CONST_MAX_STRING_LENGTH / len(str(random_float).replace('.', '')))).\
  49. replace('.','') + "." + str(random_int)
  50. STRING_ENUM = ["", None, random_str, special_character,
  51. random_str_max[:CONST_MAX_STRING_LENGTH], random_str[:CONST_MIN_STRING_LENGTH],
  52. random_start_datetime, random_end_datetime, random_date]
  53. INT_FLOAT_ENUM = [0, None, special_character, random_int, random_float,
  54. int(random_int_max[:CONST_MAX_STRING_LENGTH]),
  55. int(str(random_int)[:CONST_MIN_STRING_LENGTH]), int(random_float_max[:CONST_MAX_STRING_LENGTH]),
  56. int(str(random_float)[:CONST_MIN_STRING_LENGTH]), random_start_timestamp,
  57. random_start_timestamp_millisecond, random_end_timestamp, random_end_timestamp_millisecond]
  58. LIST_ENUM=[[], None, [{}]]
  59. BOOL_ENUM=[None, False, True,"true","false"]
  60. OBJECT_ENUM=[None,{}]
  61. NULL_ENUM=[None,"null"]
  62. # 定义接口字段生成的类型与枚举
  63. CONST_TYPE_ENUM = {
  64. str: STRING_ENUM,"string": STRING_ENUM,
  65. int: INT_FLOAT_ENUM,"integer": INT_FLOAT_ENUM,"number": INT_FLOAT_ENUM,
  66. None: NULL_ENUM, "NONE": NULL_ENUM,"null": NULL_ENUM,
  67. float: INT_FLOAT_ENUM,"double": INT_FLOAT_ENUM,
  68. bool: BOOL_ENUM,'bool': BOOL_ENUM,"boolean": BOOL_ENUM,
  69. dict: OBJECT_ENUM,'object': OBJECT_ENUM,set: LIST_ENUM,list: LIST_ENUM,'array': LIST_ENUM,
  70. }
  71. CONST_TYPE_ENUM_COPY = copy.deepcopy(CONST_TYPE_ENUM)
  72. is_array = False #判断参数传入的参数是不是list
  73. params_key_list = [] #判断参数的key对应的value是不是list类型,如果是,就将key存进此对象
  74. extra_cases = [] #存放额外的case,请求参数中的key嵌套json的情况
  75. excle_path = './'
  76. excle_name_params_field_detail = 'auto_genrate_api_case_allpairspy_params_field_detail.xlsx'
  77. excle_name_params_obj = 'auto_genrate_api_case_allpairspy_params_obj_5.xlsx'
  78. # excle_name_cases_data = 'maint-api.xlsx'
  79. excle_name_cases_data = 'maint-apiv2-api-docs.xlsx'
  80. # 递归解析json,原文:https://blog.csdn.net/qq_45662588/article/details/122265447
  81. read_excle_case_field_desc={'case':0,'uri':1,'method':2,"params_path":3,"params_body":5,"response_body":7,"skip":9}
  82. # 数据处理前的case表头,写入excle中最终会删除params_path,且params_body替换为params && response_body替换为expected
  83. write_excle_case_field_desc={'case':0,'uri':1,'method':2,'params_path':3,'params_body':4,'response_body':5,'skip':6}
  84. @classmethod
  85. def custom_valid_combination(cls,row):
  86. """
  87. 自定义过滤条件,返回false的条件不会出现在组合中
  88. """
  89. n = len(row)
  90. if n > 1:
  91. if row[0] in (None,'',""):
  92. return False
  93. return True
  94. # 指定:自定义过滤函数
  95. all_pairs_filter_func=custom_valid_combination
  96. if not (isfunction(all_pairs_filter_func) or isinstance(all_pairs_filter_func,(classmethod,staticmethod))) :
  97. raise Exception(f"all_pairs_filter_func对象必须是一个方法:{all_pairs_filter_func}")
  98. @classmethod
  99. def read_all_cases_data(cls):
  100. cases_data_obj=operationExcle(excle_path=cls.excle_path,excle_name=cls.excle_name_cases_data)
  101. lines=cases_data_obj.get_lines()+1
  102. case_list=[]
  103. for i in range(lines):
  104. # yield cases_data_obj.get_row_data(i+1)
  105. values=cases_data_obj.get_row_data(i+1)
  106. out_values=[]
  107. for i in cls.read_excle_case_field_desc:
  108. out_values.append(values[cls.read_excle_case_field_desc[i]])
  109. if out_values[0] is not None:
  110. case_list.append(out_values)
  111. return case_list
  112. @classmethod
  113. def dict_parse_generator(cls,params,pre=None):
  114. '''递归解析json,如果key为对应的值是list且list中的元素不是dict,则不返回'''
  115. pre = pre[:] if pre else [] # 纪录value对应的key信息
  116. # print("params:",params)
  117. if isinstance(params,list) :
  118. params=params[0]
  119. if isinstance(params, dict):
  120. for key, value in params.items():
  121. if isinstance(value, dict):
  122. if len(value) > 0:
  123. for d in cls.dict_parse_generator(value, pre + [key]):
  124. yield d
  125. else:
  126. yield pre + [key, '{}']
  127. elif isinstance(value, (list, tuple)):
  128. if len(value) > 0:
  129. for index, v in enumerate(value):
  130. # 纪录list的下标与key,方便动态提取
  131. # print("index",index)
  132. for d in cls.dict_parse_generator(v, pre + [key, str(index)]):
  133. yield d
  134. else:
  135. yield pre + [key, '[]'] if isinstance(value,list) else pre + [key,'()']
  136. else:
  137. yield pre + [key, value]
  138. @classmethod
  139. def api_prams_convert(cls, params_info):
  140. '''根据参数与参数类型转换为预期定义的数据类型定义的枚举'''
  141. def get_params_field_type(params_field_value):
  142. if str(params_field_value).startswith('"') and str(params_field_value).endswith('"'):
  143. _type = 'string' or 'str'
  144. elif str(params_field_value).startswith('{') and str(params_field_value).endswith("}"):
  145. _type = 'object' or 'dict'
  146. elif str(params_field_value).startswith('[') and str(params_field_value).endswith(']'):
  147. _type = 'array' or 'list'
  148. elif str(params_field_value) in ('0', '0.0'):
  149. _type = 'number' or 'int'
  150. elif str(params_field_value) in ("true", 'false', 'True', 'False'):
  151. _type = 'boolean' or 'bool'
  152. elif str(params_field_value) in ('None', 'null', 'nil'):
  153. _type = 'null' or 'None'
  154. else:
  155. _type = 'string' or 'str'
  156. return _type
  157. def replace_params_values(params_info):
  158. '''替换参数所有key的内容'''
  159. parse_params_info = list(cls.dict_parse_generator(params_info))
  160. parse_params_info_keys=[i[0] for i in parse_params_info]
  161. # 将没有输出的key添加到字典
  162. for params_info_key in params_info:
  163. if params_info_key not in parse_params_info_keys:
  164. parse_params_info.append([params_info_key,params_info[params_info_key]])
  165. for i in range(len(parse_params_info)):
  166. if parse_params_info[i] :
  167. params_key = parse_params_info[i][:-1]
  168. # 组装json提取表达式
  169. params_extract_str = "']['".join(params_key)
  170. params_extract_str = "['" + params_extract_str + "']"
  171. params_extract_str_re = re.search("\['\d+'\]", params_extract_str)
  172. if params_extract_str_re:
  173. # 去除list索引下标的字符串,处理为eval能够识别的int字符串
  174. params_extract_str_re = params_extract_str_re.group()
  175. params_extract_str = params_extract_str.replace(params_extract_str_re,
  176. params_extract_str_re.replace("'",'').replace('"',""))
  177. # 获取key内容
  178. params_field_value=eval(f"params_info{params_extract_str}")
  179. params_type=get_params_field_type(params_field_value=params_field_value)
  180. # # 将不为空得参数值且不在枚举定义范围内的数据添加到枚举定义中,避免影响原有数据且用于完善参数覆盖情况
  181. if params_field_value and (params_field_value not in cls.CONST_TYPE_ENUM[params_type]):
  182. cls.CONST_TYPE_ENUM[params_type].append(params_field_value)
  183. #动态执行代码:替换key的内容
  184. exec(f"params_info{params_extract_str}={cls.CONST_TYPE_ENUM[params_type]}")
  185. def append_params_key_is_object_to_params_key_list(params_info):
  186. '''追加参数key对应的值是object类型的key到指定list中'''
  187. if isinstance(params_info,list):
  188. params_info=params_info[0]
  189. for i in params_info:
  190. if isinstance(params_info[i],list) and isinstance(params_info[i][0],dict)\
  191. and (i not in cls.params_key_list):
  192. cls.params_key_list.append(i)
  193. if not params_info:
  194. return params_info
  195. if isinstance(params_info, (list, dict)):
  196. if isinstance(params_info, list):
  197. if params_info:
  198. params_info = params_info[0]
  199. cls.is_array = True
  200. replace_params_values(params_info)
  201. append_params_key_is_object_to_params_key_list(params_info)
  202. return params_info
  203. @classmethod
  204. def parse_uri_params_path_query(cls,uri: str, params_path: dict):
  205. '''解析url中的params_path与params_query参数'''
  206. if not params_path:
  207. return {},{}
  208. uri_split = uri.split('/')
  209. uri_params = [i for i in uri_split if i.startswith('{') and i.endswith('}')]
  210. params_path_keys = list(params_path.keys())
  211. uri_params_path_dict = {} # 存储:uri中的参数是key的情况
  212. uri_params_query_dict = {} # 存储:uri中的参数不是key的情况,代表是query参数
  213. for params_path_key in params_path_keys:
  214. if "{" + params_path_key + "}" in uri_params:
  215. uri_params_path_dict[params_path_key]=params_path[params_path_key]
  216. else:
  217. uri_params_query_dict[params_path_key]=params_path[params_path_key]
  218. return uri_params_path_dict,uri_params_query_dict
  219. @classmethod
  220. def join_request_uri(cls,uri,path_dict,query_dict):
  221. def out_path_replace_uri(uri,dict_obj):
  222. '''uri拼接替换后的path参数'''
  223. if dict_obj:
  224. for i in dict_obj:
  225. uri = uri.replace("{" + i + "}",str(dict_obj[i]) )
  226. return uri
  227. return uri
  228. def out_query_replace_uri(uri,dict_obj):
  229. '''uri拼接替换后的query参数'''
  230. if dict_obj:
  231. uri += "?"
  232. for i in dict_obj:
  233. uri += f"{i}={str(dict_obj[i])}&"
  234. uri = uri[:-1] if uri[-1] == "&" else uri
  235. return uri
  236. if not (path_dict or query_dict):
  237. return uri
  238. uri=out_path_replace_uri(uri=uri,dict_obj=path_dict)
  239. uri=out_query_replace_uri(uri=uri,dict_obj=query_dict)
  240. uri = cls.time_util.pyobject_to_json_str(uri)
  241. return uri
  242. @classmethod
  243. def out_target_case(cls,src_obj_index, params_all_obj):
  244. '''根据index返回符合条件的case'''
  245. if not params_all_obj:
  246. return {}
  247. _l = len(params_all_obj) - 1
  248. if src_obj_index > _l:
  249. target_case = params_all_obj[random.randint(0, _l)]
  250. else:
  251. target_case = params_all_obj[src_obj_index]
  252. return target_case
  253. @classmethod
  254. @exec_time_wrapper(round_num=10,module_obj=__file__,class_obj=sys._getframe().f_code.co_name,is_send_email=False)
  255. def generate_all_cases(cls, ordered_dict_obj,params_info):
  256. def convert_allpairs_cases_to_cases(params_keys,ordered_dict_obj,cases_obj):
  257. '''将allparis输出的case字符串转换为dict输出'''
  258. if len(params_keys)>=2:
  259. cls.time_util.append_params_key_isobject_to_extra_cases(case_params_key_obj=ordered_dict_obj,
  260. params_keys=params_keys,extra_cases=cls.extra_cases,filter_func=cls.all_pairs_filter_func,
  261. cases_obj=cases_obj,is_append_extra_cases=False,params_key="")
  262. else:
  263. '''params只有一个key时,直接组装字典输出(allpairs这种情况处理会输出空)'''
  264. cases = ordered_dict_obj
  265. for case in cases:
  266. for case_key in cases[case]:
  267. cases_obj.append({case: case_key})
  268. def append_extra_case_to_case(cases_obj,extra_cases,params_key_list):
  269. # 将extra_case数据追加到参数中
  270. for index,out_case in enumerate(cases_obj):
  271. for params_key in params_key_list:
  272. update_cases=[i for i in extra_cases for o in i if o == params_key]
  273. extra_case=cls.out_target_case(src_obj_index=index,params_all_obj=update_cases)
  274. out_case.update(extra_case)
  275. def return_out_cases(params_info,ordered_dict_obj):
  276. '''输出最终转换完成的测试用例集合'''
  277. if isinstance(params_info,list):
  278. params_info=params_info[0]
  279. params_keys=[i for i in params_info]
  280. out_cases = []
  281. '''将allparis输出的case字符串转换为dict输出'''
  282. convert_allpairs_cases_to_cases(params_keys=params_keys,ordered_dict_obj=ordered_dict_obj,cases_obj=out_cases)
  283. '''将extra_case数据追加到参数中'''
  284. append_extra_case_to_case(cases_obj=out_cases,extra_cases=cls.extra_cases,params_key_list=cls.params_key_list)
  285. return out_cases
  286. if not (params_info or ordered_dict_obj):
  287. return []
  288. return return_out_cases(params_info=params_info,ordered_dict_obj=ordered_dict_obj)
  289. @classmethod
  290. def rm_old_excel(cls, excle_path='./', excle_name=''):
  291. if os.path.exists(os.path.join(excle_path, excle_name)):
  292. os.remove(os.path.join(excle_path, excle_name))
  293. @classmethod
  294. def reset_params_key_list_and_extra_cases(cls):
  295. '''每个api的参数不同,在写入后需要调用此方法重置,避免元素被重复使用'''
  296. cls.params_key_list=[]
  297. cls.extra_cases=[]
  298. # 还原默认的枚举定义,避免数据一直递增
  299. cls.CONST_TYPE_ENUM=cls.CONST_TYPE_ENUM_COPY
  300. cls.is_array=False
  301. @classmethod
  302. def write_params_filed_detail_to_excle(cls, params,ex_obj:operationExcle=None):
  303. '''将参数字段明细写入到excle'''
  304. ex_obj_cp=copy.deepcopy(ex_obj)
  305. if ex_obj is None:
  306. cls.rm_old_excel(excle_path=cls.excle_path, excle_name=cls.excle_name_params_field_detail)
  307. ex_obj = operationExcle(excle_path=cls.excle_path, excle_name=cls.excle_name_params_field_detail)
  308. if not isinstance(ex_obj,operationExcle):
  309. raise Exception(f"传入的对象预期是:{type(operationExcle)},实际为:{type(ex_obj)}")
  310. # print("params:",params)
  311. if isinstance(params,dict):
  312. params=[params]
  313. ordered_dict_obj = cls.api_prams_convert(params)
  314. params_all = cls.generate_all_cases(ordered_dict_obj, params)
  315. for case in params_all:
  316. for case_key in case:
  317. # 如果key存在与list中,说明参数是list,将object转换为array
  318. if case_key in cls.params_key_list:
  319. case[case_key] = [case[case_key]]
  320. ex_obj.write_values([str(i) for i in case.values()])
  321. # 重置参数状态
  322. cls.reset_params_key_list_and_extra_cases()
  323. if ex_obj_cp is None:
  324. ex_obj.write_values(list(params_all[0].keys()))
  325. ex_obj.save_workbook()
  326. @classmethod
  327. def write_cases_obj_to_excle(cls, case_info, params_path,params_body,ex_obj:operationExcle=None):
  328. def write_cases_data_main(case_info,params_all_cases,is_body=False):
  329. '''
  330. 将生成完毕的测试用例写入到excel,基础方法(将测试用例明细写入到excle)
  331. Args:
  332. case_info: 原始的测试用例信息
  333. params_all_cases: 使用正交生成后的所有测试用例集合
  334. is_body: 用于区分请求参数是否是body,如果不是bodu,则设置params_bodu为{},因为path与query类型不需要body请求,参数直接拼接到url上了
  335. Returns:
  336. '''
  337. for index, case in enumerate(params_all_cases):
  338. # print("params_all_cases:",id(params_all_cases))
  339. path_dict = cls.out_target_case(src_obj_index=index,
  340. params_all_obj=params_all_path) if params_all_path else {}
  341. query_dict = cls.out_target_case(src_obj_index=index,
  342. params_all_obj=params_all_query) if params_all_query else {}
  343. # 拼接uri
  344. uri = cls.join_request_uri(uri=uri_copy, path_dict=path_dict, query_dict=query_dict)
  345. # print("path_dict:",path_dict)
  346. # print("query_dict:",query_dict)
  347. # # 忽略存在参数为空字符的uri
  348. # if '//' in uri:
  349. # continue
  350. case_identifying_str = '-接口异常测试用例(基于正交实验自动生成)'
  351. case_name = case_info[cls.write_excle_case_field_desc['case']]
  352. # 拼接测试用例名称
  353. if case_identifying_str in case_name:
  354. case_name = case_name[:case_name.find(case_identifying_str)]
  355. case_name = case_name + case_identifying_str + '-' + str(index + 1)
  356. case_info[cls.write_excle_case_field_desc['case']] = case_name
  357. case_info[cls.write_excle_case_field_desc['uri']] = uri
  358. for case_key in case:
  359. # 如果key存在与list中,说明参数是list,将object转换为array
  360. if case_key in cls.params_key_list:
  361. case[case_key] = [case[case_key]]
  362. # print("case:",case)
  363. # 判断最外层的参数是不是list,是list,将object转换为array
  364. if cls.is_array:
  365. case_info[cls.write_excle_case_field_desc['params_body']] = json.dumps([case], ensure_ascii=False)
  366. else:
  367. case_info[cls.write_excle_case_field_desc['params_body']] = json.dumps(case, ensure_ascii=False)
  368. if not is_body:
  369. # 从params_body中删除params_path&params_query对应的key
  370. case_info[cls.write_excle_case_field_desc['params_body']] = json.dumps({})
  371. ex_obj.write_values(case_info)
  372. # ex_obj_cp=copy.deepcopy(ex_obj)
  373. if ex_obj is None:
  374. cls.rm_old_excel(excle_path=cls.excle_path, excle_name=cls.excle_name_params_obj)
  375. ex_obj = operationExcle(excle_path=cls.excle_path, excle_name=cls.excle_name_params_obj)
  376. if not isinstance(ex_obj,operationExcle):
  377. raise Exception(f"传入的对象预期是:{type(operationExcle)},实际为:{type(ex_obj)}")
  378. uri=case_info[cls.write_excle_case_field_desc['uri']]
  379. uri_copy=copy.deepcopy(uri)
  380. # 根据params_path对象区分uri_path参数、uri_query参数
  381. uri_params_path_dict,uri_params_query_dict=cls.parse_uri_params_path_query(uri=uri_copy,params_path=params_path)
  382. # 输出path与body的ordered_dict对象
  383. ordered_dict_obj_path = cls.api_prams_convert(uri_params_path_dict)
  384. ordered_dict_obj_query = cls.api_prams_convert(uri_params_query_dict)
  385. # 输出path与body所有的正交测试用例
  386. params_all_path = cls.generate_all_cases(ordered_dict_obj_path, uri_params_path_dict)
  387. params_all_query = cls.generate_all_cases(ordered_dict_obj_query, uri_params_query_dict)
  388. ordered_dict_obj_body = cls.api_prams_convert(params_body)
  389. params_all_body = cls.generate_all_cases(ordered_dict_obj_body, params_body)
  390. if params_all_body:
  391. write_cases_data_main(case_info=case_info,params_all_cases=params_all_body,is_body=True)
  392. elif params_all_path:
  393. write_cases_data_main(case_info=case_info, params_all_cases=params_all_path, is_body=False)
  394. elif params_all_query:
  395. write_cases_data_main(case_info=case_info, params_all_cases=params_all_query, is_body=False)
  396. # 重置参数状态
  397. cls.reset_params_key_list_and_extra_cases()
  398. @classmethod
  399. @exec_time_wrapper(round_num=10,module_obj=__file__,class_obj=sys._getframe().f_code.co_name,is_send_email=True)
  400. def batch_write_params_filed_detail_to_excle(cls,case_list=None):
  401. if case_list is None:
  402. case_list=cls.read_all_cases_data()
  403. cls.rm_old_excel(excle_path=cls.excle_path, excle_name=cls.excle_name_params_field_detail)
  404. ex_obj = operationExcle(excle_path=cls.excle_path, excle_name=cls.excle_name_params_field_detail)
  405. if isinstance(case_list,dict):
  406. case_list=[case_list]
  407. # 写入首行数据
  408. for params in case_list[1:]:
  409. # params是一个可变类型,程序执行中共用了此对象,这里需要传一个深拷贝对象给他,避免执行过程中params被替换
  410. params=copy.deepcopy(params)
  411. params_body=params[cls.write_excle_case_field_desc['params_body']]
  412. sheet_name=params[cls.write_excle_case_field_desc['case']][:31]
  413. sheet_name=cls.time_util.check_filename(sheet_name,priority_matching_chars=[':','/','\\','?','*','[',']'])
  414. # print("sheet_name:",sheet_name)
  415. ex_obj.create_sheet(sheet_name)
  416. ex_obj.data=ex_obj.get_data_for_sheet_name(sheet_name)
  417. if not isinstance(params_body, dict):
  418. params_body = json.loads(params_body)
  419. if isinstance(params_body,list):
  420. params_body=params_body[0]
  421. ex_obj.write_values(list(params_body.keys()))
  422. cls.write_params_filed_detail_to_excle(params=params_body,ex_obj=ex_obj)
  423. # 删除默认创建的sheet
  424. ex_obj.delete_sheet(sheet_name='Sheet')
  425. ex_obj.save_workbook()
  426. @classmethod
  427. @exec_time_wrapper(round_num=10,module_obj=__file__,class_obj=sys._getframe().f_code.co_name,is_send_email=False)
  428. def batch_write_cases_obj_to_excle(cls,case_info=None,case_list=None):
  429. if case_list is None:
  430. case_list=cls.read_all_cases_data()
  431. if isinstance(case_list, dict):
  432. case_list = [case_list]
  433. case_info_first_line=case_info
  434. if case_info is None:
  435. case_info_first_line=list(cls.write_excle_case_field_desc.keys())
  436. # 删除params_path, 且params_body替换为params & & response_body替换为expected
  437. case_info_first_line[cls.write_excle_case_field_desc['params_body']]='params'
  438. case_info_first_line[cls.write_excle_case_field_desc['response_body']]='expected'
  439. # 删除旧的文件
  440. cls.rm_old_excel(excle_path=cls.excle_path, excle_name=cls.excle_name_params_obj)
  441. ex_obj = operationExcle(excle_path=cls.excle_path, excle_name=cls.excle_name_params_obj)
  442. ex_obj.write_values(case_info_first_line)
  443. for case in case_list[1:]:
  444. # params是一个可变类型,程序执行中共用了此对象,这里需要传一个深拷贝对象给他,避免执行过程中params被替换
  445. case_info=copy.deepcopy(case)
  446. # if case_info[1]==:
  447. if case_info[1] in (
  448. # '/v1/work-order/{tenantId}/{aggregateId}/repair-info',
  449. # # # # # '/v1/wx/user/logout',
  450. # # # # '/v1/wx/work-order/{tenantId}/{userId}/my-work-orders',
  451. # # '/v1/work-order/tenant/{tenantId}',
  452. '/v1/stakes/section/{sectionId}/number/{stakeSerialNumber}',
  453. # # '/v1/maintenance/search/select-explicit',
  454. # # '/v1/collector/find-aggregate',
  455. ):
  456. params_body=case_info[cls.write_excle_case_field_desc['params_body']]
  457. params_path=case_info[cls.write_excle_case_field_desc['params_path']]
  458. if not isinstance(params_body,dict):
  459. params_body=json.loads(params_body)
  460. if not isinstance(params_path,dict):
  461. params_path=json.loads(params_path)
  462. if params_body in ({},[],[{}],None) and params_path in ({},[],[{}],None) :
  463. case_info[cls.write_excle_case_field_desc['params_body']]=json.dumps(params_body)
  464. case_info[cls.write_excle_case_field_desc['params_path']] = json.dumps(params_path)
  465. ex_obj.write_values(case_info)
  466. else:
  467. cls.write_cases_obj_to_excle(case_info=case_info,params_path=params_path,params_body=params_body,ex_obj=ex_obj)
  468. # 删除params_path对应的列
  469. ex_obj.del_ws_cols(cls.write_excle_case_field_desc['params_path']+1,is_save=False)
  470. #重命名sheet_name
  471. ex_obj.rename_sheet_name(src_sheet_name='Sheet',target_sheet_name='all_api_info')
  472. ex_obj.save_workbook()
  473. if __name__ == "__main__":
  474. params = {"account": "demo", "pwd": "crmeb.com", "key": "533721295cb06314f4bcaacebc28e3bd", "code": "nbcw",
  475. "wxCode": "", 'userinfo': {}, 'age': 0, 'is_vip': False, 'ext': None,
  476. 'orders': [{"order": "asc", "fileds": []}], 'orders2': [{"order2": "asc", "fileds2": []}],
  477. "price2":0.05,"ip":""}
  478. test_prams=OrderedDict([
  479. ('k1',[1,2,3]),
  480. # ('k2',[('order',[1,2,3]),('order2',[4,5,6])])
  481. ])
  482. params_array = [params]
  483. params_array.extend(params_array)
  484. #
  485. auto_case = autoGenrateApiCaseAllpairspy()
  486. path_dict = {'sectionId': 3448477344847734484773448477344847734484773448477344847734484773448477344847734484773448477344847734, 'stakeSerialNumber': '留言一点重要.文件发展就是资料论坛制作.留言一点重要.文件发展就是资料论坛制作.留言一点重要.文件发展就是资料论坛制作.留言一点重要.文件发展就是资料论坛制作.留言一点重要.文件发展就是资料论坛制作.'}
  487. query_dict = {"tenantId2": 22,"df":"432fsfds"}
  488. # url=auto_case.join_request_uri(uri="/v1/stakes/section/{sectionId}/number/{stakeSerialNumber}",path_dict=path_dict,query_dict=query_dict)
  489. # print(url)
  490. # json_str={'breakageThreshold': {}, 'breakageType': ['linear', 'alligator', 'pothole', 'raveling', 'explicit', 'subsidence', 'rut', 'seal', 'patch', 'horizontalCrack', 'verticalCrack'], 'endTime': 0, 'startTime': 0, 'tenantId': 0}
  491. # print(list(auto_case.dict_parse_generator(json_str)))
  492. # # 读取case数据
  493. # print(auto_case.read_all_cases_data())
  494. # auto_case.batch_write_cases_obj_to_excle()
  495. # auto_case.join_uri_params_path('/v1/stakes/section/{sectionId}/number/{stakeSerialNumber}',{"sectionId": 0, "stakeSerialNumber": "","test":123})
  496. # 单个写入
  497. # auto_case.write_params_filed_detail_to_excle(params)
  498. # auto_case.write_cases_obj_to_excle(case_info, params=params_array)
  499. # 批量写入
  500. # auto_case.batch_write_params_filed_detail_to_excle()
  501. # print("case_info:before",case_info)
  502. auto_case.batch_write_cases_obj_to_excle()
  503. # print("case_info:after",case_info)

python实战-基于正交实验(工具:allpairs)自动生成接口异常测试用例的更多相关文章

  1. 基于MATLAB2016b图形化设计自动生成Verilog语言的积分模块及其应用

    在电力电子变流器设备中,常常需要计算发电量,由于电力电子变流器设备一般是高频变流设备,所以发电量的计算几乎时实时功率的积分,此时就会用到一个积分模块.发电量计算的公式如下:Q=∫P. FPGA由于其并 ...

  2. Django框架深入了解_05 (Django中的缓存、Django解决跨域流程(非简单请求,简单请求)、自动生成接口文档)

    一.Django中的缓存: 前戏: 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面. 当一个网站的用户访问量很大的时候,每一 ...

  3. rbac介绍、自动生成接口文档、jwt介绍与快速签发认证、jwt定制返回格式

    今日内容概要 RBAC 自动生成接口文档 jwt介绍与快速使用 jwt定制返回格式 jwt源码分析 内容详细 1.RBAC(重要) # RBAC 是基于角色的访问控制(Role-Based Acces ...

  4. .net core 使用swagger自动生成接口文档

     前言 swagger是一个api文档自动生动工具,还集成了在线调试. 可以为项目自动生成接口文档, 非常的方便快捷 Swashbuckle.AspNetCore 是一个开源项目,用于生成 ASP.N ...

  5. Spring Boot(九)Swagger2自动生成接口文档和Mock模拟数据

    一.简介 在当下这个前后端分离的技术趋势下,前端工程师过度依赖后端工程师的接口和数据,给开发带来了两大问题: 问题一.后端接口查看难:要怎么调用?参数怎么传递?有几个参数?参数都代表什么含义? 问题二 ...

  6. swagger 自动生成接口测试用例

    ---整体更新一波--- 1.实际工作中,因为要动手输入的地方比较多,自动生成的异常接口用例感觉用处不大,就先去掉了,只保留了正常的: 2.接口有改动的,如果开发人员没有及时告知或没有详细告知,会增加 ...

  7. drf07 过滤 排序 分页 异常处理 自动生成接口文档

    4. 过滤Filtering 对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展来增强支持. pip install django-filter 在配置文件sett ...

  8. drf频率源码、自动生成接口文档、JWT

    目录 一.drf频率源码分析 二.自动生成接口文档 1 安装依赖 2 设置接口文档访问路径 3 文档描述说明的定义位置 4 访问接口文档网页 三.JWT 1 JWT基本原理 1.1 header 1. ...

  9. day74:drf:drf其他功能:认证/权限/限流/过滤/排序/分页/异常处理&自动生成接口文档

    目录 1.django-admin 2.认证:Authentication 3.权限:Permissions 4.限流:Throttling 5.过滤:Filtering 6.排序:OrderingF ...

  10. JApiDocs(自动生成接口文档神器)

    JApiDocs教程 前言 作为一名优秀的程序员来说,由于涉及到要与前端进行对接,所以避免不了的就是写接口文档.写完接口文档,一旦代码返回结果,参数等出现变动,接口文档还得随之改动,十分麻烦,违背了我 ...

随机推荐

  1. 十七、Job与Cronjob

    Job 与 Cronjob 一.Job ​Job 负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束. 特殊说明: 1.spec.template 格式同 Pod ​2 ...

  2. C++实现真值表

    这一片文章主要是关于真值表,在完成之前我也遇到了许多问题.比如怎么去求解表达式的值,怎么去将每个变量进行赋值,也就是如何 将n个字符进行01全排列. 01全排列真的神奇,01全排列其实就是2^n.他可 ...

  3. mindxdl--common--validators.go

    // Copyright (c) 2021. Huawei Technologies Co., Ltd. All rights reserved.// Package common this file ...

  4. 云原生之旅 - 13)基于 Github Action 的自动化流水线

    前言 GItHub Actions是一个持续集成和持续交付的平台,能够让你自动化你的编译.测试和部署流程.GitHub 提供 Linux.Windows 和 macOS 虚拟机来运行您的工作流程,或者 ...

  5. 列表、集合、元组、字典、range

    #列表y = [1,2,3]# 追加y.append(4)print(y)#删除del y[3]print(y)#查询存放个数print(len(y))#查询位置内容print(y[0]) #正序pr ...

  6. 华为云平台部署教程之CNA\VRM的安装

    本教程仅含华为云平台搭建部署中CNA和VRM的安装,请按需求选择查看本文. 一.前期准备 1.硬件 服务器*4 交换机*3 网线 个人PC机 2.软件 PC机系统(win7/win10) KVM软件 ...

  7. 用最少的代码模拟gRPC四种消息交换模式

    我们知道,建立在HTTP2/3之上的gRPC具有四种基本的通信模式或者消息交换模式(MEP: Message Exchange Pattern),即Unary.Server Stream.Client ...

  8. PostgreSQL函数:查询包含时间分区字段的表,并更新dt分区为最新分区

    一.需求 1.背景 提出新需求后,需要在www环境下进行验收.故需要将www环境脚本每天正常调度 但由于客户库无法连接,ods数据无法每日取,且连不上客户库任务直接报错,不会跑ods之后的任务 故需要 ...

  9. Spring02:注解IOC、DBUtils单表CRUD、与Junit整合

    今日内容:基于注解的IOC及IOC的案例 Spring中IOC的常用注解 案例-使用xml方式和注解方式实现单表的CRUD操作 持久层技术选型:DBUtils 改造基于注解的IOC案例,使用纯注解的方 ...

  10. 【面试题总结】Java并发-多线程、JUC详述(思维导图)

    〇.整体目录 一.多线程 1.实现方式 2.内存图 3.线程状态 4.实现线程同步 5.并发编程 二.JUC 1.概述与volatile关键字 2.ThreadLocal类 3.CAS方法 4.ato ...