1.需求背景:
我们在进行接口请求时需要用到各种各样的数据格式,比如随机唯一值,时间戳等等这些可以通过参数化函数来实现httprunner在实现上也参考了jm的类似思想设计 :
我们如果做平台化时,就可以实现类似debugtalk的设计思路来实现参数化函数自定义使用:
设计思路:
1.动态加载模块debugtalk里的方法并且获取参数和返回值:
2.请求参数提取出函数并且判断它是否在debugtalk加载出来对象方法里,如果在就执行替换方法位里面方法执行返回值:
第一步,编写debugtalk 定义函数钩子
第二步:编写动态加载提取方法mapping:
import types import importlib import ast import re import json import os def parse_string_value(str_value): """ :param str_value: '123'==>123 :return: """ try: return ast.literal_eval(str_value) except ValueError: return str_value except SyntaxError: # e.g. $var, ${func} return str_value def load_module_functions(module): """ load debugtalk functions mapping """ module_functions = {} for name, item in vars(module).items(): if isinstance(item, types.FunctionType): module_functions[name] = item return module_functions def parse_function_params(params): """ parse the function params and return it example: parse_function_params("1, 2, a=3, b=4") :return: {'args': [1, 2], 'kwargs': {'a':3, 'b':4}} """ function_meta = { "args": [], "kwargs": {} } params_str = params.strip() if params_str == "": return function_meta args_list = params_str.split(',') for arg in args_list: arg = arg.strip() if '=' in arg: key, value = arg.split('=') function_meta["kwargs"][key.strip()] = parse_string_value(value.strip()) else: function_meta["args"].append(parse_string_value(arg)) return function_meta def extra_func_name(data: dict): """ extract method name list of data value :return : ['__RandomInt(5,8)}}', '__UUID1()'] ect. """ d = json.dumps(data, separators=(',', ':')) funcs = re.findall(r"{{(.*?)}}", d) return funcs def hook_replace(data: dict)->dict: """ :function: replace the function with debugtalk function's return result :param data: {"name": "${{__RandomInt(5,8)}}", "foo2": "${{__UUID1()}}"} :return: dict """ dump_string = json.dumps(data) imported_module = importlib.import_module("mysite.debugtalk") mapping = extra_func_name(data) for method_name in mapping: function_name = re.findall("(.*?)[(]", method_name)[0] func_mapping = load_module_functions(imported_module) function = func_mapping.get(function_name) if function: params = re.findall(function.__name__ + "[(](.*?)[)]", method_name)[0] args_kwargs = parse_function_params(params) args, kwargs = args_kwargs.get("args"), args_kwargs.get("kwargs") if not args and not kwargs: res = function() if isinstance(res, (int, float, list)): ret = dump_string.replace('"${{' + method_name + '}}"', json.dumps(res)) dump_string = ret else: ret = dump_string.replace('${{' + method_name + '}}', str(res)) dump_string = ret else: res = function(*args, **kwargs) if isinstance(res, (int, float, list)): ret = dump_string.replace('"${{' + method_name + '}}"', json.dumps(res)) dump_string = ret else: ret = dump_string.replace('${{' + method_name + '}}', str(res)) dump_string = ret return json.loads(dump_string)
其中hook_replace 方法func_mapping 打印出来是这样:
{'__RandomString': <function __RandomString at 0x00000185B901A048>, '__RandomInt': <function __RandomInt at 0x00000185B9114510>,
可以通过key获取到function:
而key可以提取data = {"name": "${{__RandomInt(5,8)}}", "foo2": "${{__UUID1()}}"},通过正则提取:
def extra_func_name(data: dict):
"""
extract method name list of data value
:return : ['__RandomInt(5,8)}}', '__UUID1()'] ect.
"""
d = json.dumps(data, separators=(',', ':'))
funcs = re.findall(r"{{(.*?)}}", d)
return funcs
然后迭代数组mapping,再次提取函数名称function_name,这样就可以通过function_name 为key拿到func_mapping 的对应函数对象,
接下来就需要获取参数了,入参*args,**kwargs:
params = re.findall(function.__name__ + "[(](.*?)[)]", method_name)[0]
把参数解析通过args_kwargs = parse_function_params(params)返回:
{'args': [1, 2], 'kwargs': {'a':3, 'b':4}} 或者 {}
由于并不是所有的函数都有入参所以需要判断
if not args and not kwargs:
res = function()
else:
res = function(*args, **kwargs)
最后就是替换函数助手为实际返回值了,分str一种情况,int,float list 一种情况,因为有的参数要求请求时就是int类型保持原有数据类型:
if isinstance(res, (int, float, list)):
ret = dump_string.replace('"${{' + method_name + '}}"', json.dumps(res))
dump_string = ret
else:
ret = dump_string.replace('${{' + method_name + '}}', str(res))
dump_string = ret
最后就是关于动态加载模块知识: