前言
pytest 提供了一个收集用例的钩子,在用例收集阶段,默认会查找test_*.py 文件或者 *_test.py文件。
如果我们想运行一个非python的文件,比如用yaml 文件写用例,那么就需要改变用例的收集规则。
以最新版pytest 7.2.0版本为例
YAML 测试示例
在 Yaml 文件中指定测试的基本示例, 以下是官方文档上给的一个执行yaml格式的内容作为自定义测试的例子。
相关文档地址https://docs.pytest.org/en/latest/example/nonpython.html
写到conftest.py
# content of conftest.py import pytest def pytest_collect_file(parent, file_path): if file_path.suffix == ".yaml" and file_path.name.startswith("test"): return YamlFile.from_parent(parent, path=file_path) class YamlFile(pytest.File): def collect(self): # We need a yaml parser, e.g. PyYAML. import yaml raw = yaml.safe_load(self.path.open()) for name, spec in sorted(raw.items()): yield YamlItem.from_parent(self, name=name, spec=spec) class YamlItem(pytest.Item): def __init__(self, *, spec, **kwargs): super().__init__(**kwargs) self.spec = spec def runtest(self): for name, value in sorted(self.spec.items()): # Some custom test execution (dumb example follows). if name != value: raise YamlException(self, name, value) def repr_failure(self, excinfo): """Called when self.runtest() raises an exception.""" if isinstance(excinfo.value, YamlException): return "n".join( [ "usecase execution failed", " spec failed: {1!r}: {2!r}".format(*excinfo.value.args), " no further details known at this point.", ] ) def reportinfo(self): return self.path, 0, f"usecase: {self.name}" class YamlException(Exception): """Custom exception for error reporting."""
创建一个简单的yaml文件
# test_simple.yaml ok: sub1: sub1 hello: world: world some: other
如果你已经安装了 PyYAML 或 YAML-parser解析器,那么就可以执行yaml用例
nonpython $ pytest test_simple.yaml =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project/nonpython collected 2 items test_simple.yaml F. [100%] ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. ========================= short test summary info ========================== FAILED test_simple.yaml::hello ======================= 1 failed, 1 passed in 0.12s ========================
网上关于 pytest 插件开发的资料非常少,大部分都是停留在使用 pytest 写用例的阶段。
也有一些 pytest+yaml 的封装,最终还是会写的 py 文件去读取 yaml 文件执行用例,并没有达到真正意义上的把 yaml 文件当一个用例去执行。
pytest_collect_file 钩子
先看下pytest_collect_file 钩子的定义
def pytest_collect_file( file_path: Path, path: "LEGACY_PATH", parent: "Collector" ) -> "Optional[Collector]": """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. The new node needs to have the specified ``parent`` as a parent. :param file_path: The path to analyze. :param path: The path to collect (deprecated). .. versionchanged:: 7.0.0 The ``file_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``path`` parameter. The ``path`` parameter has been deprecated. """
这里用到了3个参数
- file_path 它是一个 pathlib.Path 对象, 收集到的文件路径
- path LEGACY_PATH(合法路径), 收集到的文件路径
- parent Collector 收集器,用例文件.py 或者 .yml 文件的父目录,也就是 python 的包 Package
v 7.0.0 版本的变更:
在 v 7.0.0 版本后,新增了一个 file_path 参数,它与原来的 path 功能是一样的,原来的 path 参数会被弃用。
我们看下这2个参数变更前和变更后到底用什么区别呢?
def pytest_collect_file(file_path: Path, path, parent): # 获取文件.yml 文件,匹配规则 if file_path.suffix == ".yml" and file_path.name.startswith("test"): print(file_path, type(file_path)) print(path, type(path)) print(parent, type(parent)) return YamlFile.from_parent(parent, path=file_path)
运行 pytest -s
看到打印日志
collecting ... D:demodemo_x2casetest_login.yml <class 'pathlib.WindowsPath'> D:demodemo_x2casetest_login.yml <class '_pytest._py.path.LocalPath'> <Package case> <class '_pytest.python.Package'>
原来的path参数(path.LocalPath),是通过os模块的path 获取的文件路径
最新的file_path 参数(pathlib.WindowsPath), 是通过pathlib 模块获取的文件路径。
pathlib 是 os模块的升级版,所以这里做了一个细节的优化。
通过pytest_collect_file
收集钩子就可以找到.yml
后缀,并且以test开头的文件,会被当做用例返回。
pytest_ignore_collect 忽略收集
与pytest_collect_file
勾选相反的一个忽略收集钩子pytest_ignore_collect
[docs]@hookspec(firstresult=True) def pytest_ignore_collect( collection_path: Path, path: "LEGACY_PATH", config: "Config" ) -> Optional[bool]: """Return True to prevent considering this path for collection. This hook is consulted for all files and directories prior to calling more specific hooks. Stops at first non-None result, see :ref:`firstresult`. :param collection_path: The path to analyze. :param path: The path to analyze (deprecated). :param config: The pytest config object. .. versionchanged:: 7.0.0 The ``collection_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``path`` parameter. The ``path`` parameter has been deprecated. """
也是传3个参数
- collection_path 收集到的用例文件路径,pathlib.Path类
- path 跟 collection_path作用一样,被弃用了
- config Config的实例
通过返回布尔值判断是否收集该文件
举个例子,当判断用例文件名称是test_login.yml
就不收集
def pytest_ignore_collect(collection_path: Path, path, config): # 返回布尔值(会根据返回值为 True 还是 False 来决定是否收集改路径下的用例) if collection_path.name == 'test_x.yml': return True
运行后不会收集'test_x.yml'文件