前言
在第一篇文章中,我们讲过了通过钩子函数pytest_collect_file
可以收集到yaml格式的用例,并且可以生成测试用例。
想要动态生成测试用例,也就是动态生成测试函数,然后pytest收集到test开头的func,执行测试函数。关键代码如下所示:
def run_function(*args, **kwargs):
print("测试用例-----")
# 向 module 中加入test 函数
setattr(MyModule, "test_run", run_function)
由上面可以知道,目前生成测试函数,我们是写死的run_function()。这样写虽然也能把读到的yaml文件按关键字去执行,但是定义的函数是写死的,不能给函数动
态添加一些参数。我想实现定义一个函数,动态添加不同参数,就无法实现了,于是需要找到一个可以动态生成函数的方法,并且能给函数动态添加参数。
1. 动态生成函数
目前通过查看资料,发现动态生成函数有以下几种方式:
1. 方式一:已有函数的基础上,创建新函数
2. 方式二:配合compile函数 创建函数
3. 方式三:一个函数动态创建多个函数(重要)
接下来我会使用方式三来动态创建函数的操作。该方法可以参考:https://zhuanlan.zhihu.com/p/386276353
其生成函数的过程我不讲,具体可以看上面的链接,我就直接讲怎么用,该方法的源码如下:
# utils/create_funtion.py
import sys
import types
from typing import Any, Callable, Mapping, Sequence
from inspect import Parameter, Signature
def create_function_from_parameters(
func: Callable[[Mapping[str, Any]], Any],
parameters: Sequence[Parameter],
documentation=None,
func_name=None,
func_filename=None):
new_signature = Signature(parameters) # Checks the parameter consistency
def pass_locals():
return dict_func(locals()) # noqa: F821 TODO
code = pass_locals.__code__
mod_co_argcount = len(parameters)
mod_co_nlocals = len(parameters)
mod_co_varnames = tuple(param.name for param in parameters)
mod_co_name = func_name or code.co_name
if func_filename:
mod_co_filename = func_filename
mod_co_firstlineno = 1
else:
mod_co_filename = code.co_filename
mod_co_firstlineno = code.co_firstlineno
if sys.version_info >= (3, 8):
modified_code = code.replace(
co_argcount=mod_co_argcount,
co_nlocals=mod_co_nlocals,
co_varnames=mod_co_varnames,
co_filename=mod_co_filename,
co_name=mod_co_name,
co_firstlineno=mod_co_firstlineno,
)
else:
modified_code = types.CodeType(
mod_co_argcount,
code.co_kwonlyargcount,
mod_co_nlocals,
code.co_stacksize,
code.co_flags,
code.co_code,
code.co_consts,
code.co_names,
mod_co_varnames,
mod_co_filename,
mod_co_name,
mod_co_firstlineno,
code.co_lnotab
)
default_arg_values = tuple(p.default for p in parameters if p.default != Parameter.empty) #!argdefs "starts from the right"/"is right-aligned"
modified_func = types.FunctionType(modified_code, {'dict_func': func, 'locals': locals}, name=func_name,argdefs=default_arg_values)
modified_func.__doc__ = documentation
modified_func.__signature__ = new_signature
return modified_func
def foo(arg):
print(arg)
return "x"
f = create_function_from_parameters(
func=foo,
parameters=[Parameter('x', Parameter.POSITIONAL_OR_KEYWORD)],
documentation="some doc",
func_name="bar",
func_filename="main.py",
)
# 上面等价于下面的函数
def bar(a):
"""some doc"""
foo({'x': a}) # bar函数的函数体等价于foo({’x‘: a})
# print(f('1111'))
于是我们把这个方法写到我们的钩子函数pytest_collect_file
中。
# conftest.py
from pytest import Module, Function
import types
from utils.create_function import create_function_from_parameters
from inspect import Parameter
def pytest_collect_file(file_path, parent):
# 查找test开头的文件
if file_path.suffix in ['.yml', '.yaml'] and (file_path.name.startswith('test') or file_path.name.endstartswith('test')):
print(f'yaml文件路径:{file_path}')
print(f'yaml文件路径名称:{file_path.stem}')
# 构造 pytest 框架的 Module,module由Package构建,Package由系统构建
py_module = Module.from_parent(parent, path=file_path)
# 动态创建测试module模块(.py文件),这里才能给下面的_getobj进行导入
MyModule = types.ModuleType(file_path.stem)
from utils.read_file import read_yaml
from pathlib import Path
file_path = Path(__file__).parent.joinpath('data', 'login.yml')
raw_data = read_yaml(file_path)['data']
for key, value in raw_data:
def foo(arg):
print(f"执行的参数: {arg}")
print(f"执行的内容: {value}")
f = create_function_from_parameters(func=foo,
parameters=[Parameter('request', Parameter.POSITIONAL_OR_KEYWORD)],
documentation="some doc",
func_name=key,
func_filename="main.py")
# 向 module 中加入test 函数
setattr(MyModule, key, f)
# 重写_getobj,返回自己定义的Mymodule
py_module._getobj = lambda: MyModule
return py_module
我们在项目目录下创建一个test_x.yml文件,来测试是否动态创建成功。
# test_x.yml
test_x1:
print: xxxxxx111
test_x2:
print: xxx2222
pytest .\test_x.yml -s
执行后,如下图,发现动态创建成功,且request
参数也动态传入。
参考来源:https://zhuanlan.zhihu.com/p/386276353
标签:code,co,file,用例,05,name,yaml,func,mod From: https://www.cnblogs.com/dack-zt-deng/p/17860400.html