关键字 开发-06 封装yaml文件直接生成测试用例

发布时间 2023-11-28 21:12:03作者: dack_deng

前言

前面几个章节,我们主要是如何通过yaml文件的数据自动转换成测试用例,并按照关键字去执行,如下是之前我们通过参数化的形式手动执行用例。

from utils.run import RunByKey

# 获取文件路径
file_path = Path(__file__).parent.joinpath('data', 'login.yml')

@pytest.mark.parametrize('key, value', read_file.read_yaml(file_path)['data'])
def test_login(key, value, base_url):
    print(f"测试用例名称: {value['name']}")
    print(f"测试数据: {value['request']}")  # data ---dict
    print(f"base_url: {base_url}")
    s = HttpSession(base_url=base_url)
    run = RunByKey(s, read_file.read_yaml(file_path)['config'])
    run.run(value)

现在我们继续封装,不进行手动参数化方式执行用例。也就是我们连上面的代码也无需写,只通过yaml文件直接执行用例。
如果认真点,我们就会发现,在05章节中的最后一张图中,也就是我们动态生成测试用例函数的时候,我们会发现一个问题:如下所示:

1.Python经典问题

上面的问题我们叫做python经典问题,如何去解决这个问题呢?
首先我们举个例子,来分析下这个问题形成的过程:

res = []
data = {"testa": "aaa", "testb": "bbb"}
for key, value in data.items():
  def fun():
    print(value)
  res.append(fun)
print(res)
for f in res:
f()

打印结果:
[<function fun at 0x0000020D5DC76280>, <function fun at 0x0000020D5DDFC280>]
bbb
bbb

分析:

1.for 循环data里面的数据,函数在循环内部定义,但是未调用
2.在for循环内部将函数对象添加到res列表中
3.for循环调用函数f(),而此时的value值取最新的为第二个“bbb”,于是打印2个bbb

1.1 解决函数对象调用后,内部值被覆盖的问题

解决思路,我们首先需要确定函数被谁调用, 然后知道被谁调用后单独取出这个函数对应的值。

import inspect

import inspect
def fun():
    # 被谁调用
    call_function_name = inspect.getframeinfo(inspect.currentframe().f_back)[2]
    print(call_function_name)
    return "33"
def fun_x():
    fun()
def fun_y():
    fun()
fun_x()
fun_y()

打印:
fun_x
fun_y

于是我们在执行yaml的函数中,添加这个方法:

from pytest import Module, Function
import types
import yaml
from utils.run import RunYaml
import inspect
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}")
                call_function_name = inspect.getframeinfo(inspect.currentframe().f_back)[2]
                raw_data_dict = dict(raw_data)
                print(f"执行的内容: {raw_data_dict[call_function_name]}")

            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