前言
使用yaml作为测试用例,我们就需要对文件的内容进行读取,常规来说的应该是通过pyyaml对读取到的内容进行数据解析,然后使用pytest parametrize参数化功能进行数据参数化用例测试。但是完事之后,这样的方式好像不是很优雅,写的代码组织起来比较费劲,于是乎,我在pytest的官方文档中,发现了一套更为一套非常优雅的测试执行方式,他们称之为non-python test的测试模式。
具体内容可以查看官方文档,感兴趣的可以去看看:Working with non-python tests — pytest documentation
# content of conftest.py
import pytest
def pytest_collect_file(parent, path):
if path.ext == ".yaml" and path.basename.startswith("test"):
return YamlFile.from_parent(parent, fspath=path)
class YamlFile(pytest.File):
def collect(self):
# We need a yaml parser, e.g. PyYAML.
import yaml
raw = yaml.safe_load(self.fspath.open())
for name, spec in sorted(raw.items()):
yield YamlItem.from_parent(self, name=name, spec=spec)
class YamlItem(pytest.Item):
def __init__(self, name, parent, spec):
super().__init__(name, parent)
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.fspath, 0, f"usecase: {self.name}"
class YamlException(Exception):
"""Custom exception for error reporting."""
可以看到官方文档中以极其优雅的方式通过yaml文件驱动了两个测试用例。我们也将在此基础上进行扩展衍生。
读取yaml文件
我们根据官方文档中的示例文件,在这个基础上进行修改,加入我们的内容。
pytest_collect_file
首先我们修改pytest_collect_file
函数中的内容,让他支持yaml
和yml
两种格式的文件内容。因为这两种都可以,官网示例中只有一个。
YamlFile.collect
接下来修改我们的YamlFile.collect
方法,这里面就是对读出来的详细内容按照设置的格式进行处理,该存入缓存的放入缓存,该执行测试的时候执行测试。
if not any(k.startswith('test') for k in raw.keys()):
raise YamlException("{}yaml non test found".format(self.fspath))
通过这个语句我们先判断一下,有没有测试用例,如果没有测试用例我们直接就报错了,不在执行,抛出异常,这个异常需要我们自己封装一下。我们打开common/exceptions.py
文件。输入以下内容:
# -*- coding: utf-8 -*-
__author__ = 'wxhou'
__email__ = '1084502012@qq.com'
"""
异常类
"""
from requests.exceptions import RequestException
class YamlException(Exception):
"""Custom exception for error reporting."""
def __init__(self, value):
self.value = value
def __str__(self):
return "\n".join(
[
"usecase execution failed",
" spec failed: {}".format(self.value),
" For more details, see this the document.",
]
)
这个就是当我们发现yaml文件中没有符合的测试标签内容后抛出的异常类。
然后我们接着先读取全局变量:
if variable := raw.get('variable'):
for k, v in variable.items():
cache.set(k, v)
我们把yaml文件中预设的全局变量信息中全部存在我们设置的缓存模块中,这样在测试过程中我们可以随时的去用。
继续读取配置文件。
if config := raw.get('config'):
for k, v in config.items():
cache.set(k, v)
然后我们读取常用的测试信息也放入缓存之中,方便运行过程中随时去调用。
最后我们来处理一下。测试用例部分:
if tests := raw.get('tests'):
for name, spec in tests.items():
yield YamlTest.from_parent(self,
name=spec.get('description') or name,
spec=spec)
可以看到,在官方文档中使用了sorted函数进行了再次排序。我这里没有是因为再次排序会破坏用例的结构和顺序。最后输出的时候spec.get('description') or name
的写法先获取yaml文件中我们设置的中文标识,如果中文标识不存在则继续使用英文标识。其余和官方文档保持一致。
以上就是做出的改动,我们来看看吧:
import yaml
import pytest
from common.cache import cache
from common.exceptions import YamlException
def pytest_collect_file(parent, path):
if path.ext in (".yaml", ".yml") and path.basename.startswith("test"):
return YamlFile.from_parent(parent, fspath=path)
class YamlFile(pytest.File):
def collect(self):
raw = yaml.safe_load(self.fspath.open(encoding='utf-8'))
if not any(k.startswith('test') for k in raw.keys()):
raise YamlException("{}yaml non test found".format(self.fspath))
if variable := raw.get('variable'):
for k, v in variable.items():
cache.set(k, v)
if config := raw.get('config'):
for k, v in config.items():
cache.set(k, v)
if tests := raw.get('tests'):
for name, spec in tests.items():
yield YamlTest.from_parent(self,
name=spec.get(
'description') or name,
spec=spec)
站在巨人的肩膀上才能看得更远。在pytest non-python tests的内容之上做了一些改动,使得读取文件更加贴合我们定义的yaml文件内容。在精简了很多代码的同时我们也达到了预期的效果。
至此,本章的读取yaml测试文件到此结束。