1.需求背景:

我们在进行接口请求时需要用到各种各样的数据格式,比如随机唯一值,时间戳等等这些可以通过参数化函数来实现httprunner在实现上也参考了jm的类似思想设计 :

自定义实现httprunner debugtalk 的函数助手功能_json

 

 

我们如果做平台化时,就可以实现类似debugtalk的设计思路来实现参数化函数自定义使用:

设计思路:

1.动态加载模块debugtalk里的方法并且获取参数和返回值:

2.请求参数提取出函数并且判断它是否在debugtalk加载出来对象方法里,如果在就执行替换方法位里面方法执行返回值:

第一步,编写debugtalk 定义函数钩子

自定义实现httprunner debugtalk 的函数助手功能_json_02

 

 第二步:编写动态加载提取方法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

最后就是关于动态加载模块知识:

自定义实现httprunner debugtalk 的函数助手功能_动态加载_03