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

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

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

原文地址:https://www.cnblogs.com/SunshineKimi/p/15048900.html