关键字 开发-01 pytest收集yaml用例

发布时间 2023-11-23 10:20:14作者: dack_deng

1.pytest收集测试用例规则

pytest框架在收集用例的时候,默认会查找test_开头的.py 模块,并且在模块中找到test_ 开头的函数或Test开头的类。
根据这个规则,如果我们想收集yaml格式的文件当用例执行,需解决2个问题:

1.修改用例收集规则,改成收集yaml格式文件作为收集的目标
2.将yaml文件生成pytest的测试用例

pytest 在运行的时候,以你运行的目录,往上查找 conftest.py 文件,以 pytest.ini 所在的目
录为根路径。

test_a.py

def test_1():
    print('hello')

def test_2():
    print('world')

打断点运行查看:

从图中我们可以清楚的知道:

1.每个用例最终会转成 一个 Function 对象:
2.Function 对象的上一层(parent ) 是 Module 模块对象:Module test_a.py
3.Module 模块的上一层是 Package 包,Package 包的上一层是 session 对象

于是我们知道pytest 收集用例的基本思路:

2. 如何构造pytest的item用例

从上面可以知道,构造pytest的item,需要:1.构造Package,2.构造Module, 3.构造Function
查看Function的源码:> from pytest import Function, Module

于是可以知道,基本思想是利用 Module 构建 Function!其调用伪代码如下: > Function.from_parent(Module)
既然可以利用 Module 构建 Function, 那如何构建 Module ? 当然是利用 Package 构建
Module!> Module.from_parent(Package)
既然可以利用 Package 构建 Module 那如何构建 Package ? 请看下图调用关系:

pytest 从 Config 开始,层层构建,直到 Function !Function 是 pytest 的最小执行单元。 手动
构建 item 就是模拟 pytest 构建 Function 的过程。也就是说,需要创建 Config ,然后利用
Config 创建 Session ,然后利用 Session 创建 Package ,…,最后创建 Function。
pytest 会自动创建好 Config, Session 和 Package ,这三者不用手动创建。

2. yaml文件构建成Module模块喝用例

先通过 pytest_collect_file 钩子收集到指定yaml文件用例。

# conftest.py
from pytest import Module
import types
def pytest_collect_file(file_path, parent):
  # 查找test 开头的yaml 文件
  if file_path.suffix in [".yml", ".yaml"] and (file_path.name.startswith("test") or file_path.name.endswith("test")):
    print(f"yaml 文件的路径:{file_path}")
    print(f"yaml 文件的名称:{file_path.stem}")
    # 构造 pytest 框架的 Module
    py_module = Module.from_parent(parent, path=file_path)
    # 动态创建
    MyModule = types.ModuleType(file_path.stem)
    # 重写_getobj
    py_module._getobj = lambda: MyModule
    return py_module

步骤1:先通过Module 类构造pytest 框架的 Module 用例模块

构造 pytest 框架的 Module
py_module = Module.from_parent(parent, path=file_path)

由于是一个yaml 文件,它不是一个真正的py模块,Module 类里面有个 _getobj 方法是 import
python的模块,yaml 文件是不能import 的,所以我们需要重新 _getobj 方法
以下是Module 类相关源码

class Module(nodes.File, PyCollector):
  """Collector for test classes and functions."""
  def _getobj(self):
    return self._importtestmodule()

步骤2:yaml 文件如何变成 py模块?这是解决问题的关键点,其实很简单,我们自己动态构造一个模块
就行了,一句代码就能解决.

动态创建模块
MyModule = types.ModuleType('test_x')

步骤3:到这里问题解决了一半,yaml 文件已经被我们动态构造成了 pytest 框架的 Module。
接下来我们需要解决另外一个问题,通过 Module 构造生成 Function 用例。Function 用例不需要再用类似于 Function.from_parent(parent) 这种方式构建了,我们只需要
给 Module 模块对象添加test开头的函数,它就会自动变成用例。

def run_function(*args, **kwargs):
  print("执行测试用例-----")
  # 向 module 中加入test 函数
  setattr(MyModule, "test_run", run_function)

完整的 conftest.py 中代码如下:

from pytest import Function, Module
import types
 def pytest_collect_file(file_path, parent):
  # 查找test 开头的yaml 文件
  if file_path.suffix in [".yml", ".yaml"] and (file_path.name.startswith("test") or file_path.name
    print(f"yaml 文件的路径:{file_path}")
    print(f"yaml 文件的名称:{file_path.stem}")
    # 构造 pytest 框架的 Module
    py_module = Module.from_parent(parent, path=file_path)
    # 动态创建
    MyModule = types.ModuleType(file_path.stem)
    def run_function(*args, **kwargs):
    print("测试用例-----")
    # 向 module 中加入test 函数
    setattr(MyModule, "test_run", run_function)
    # 重写_getobj
    py_module._getobj = lambda: MyModule
    return py_module

然后我们随便写一个空的 test_x.yaml 文件,它都能执行了

到这里我们所做的事情就非常清晰了,把yaml 文件动态生成 python 的模块。并通过钩子函数方法构造成pytest 框架的 Module 对象。
yaml文件的内容暂时是写死的一个固定函数执行,下一篇讲读取yaml文件内容按关键字执行。