前言
使用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
兩種格式的文件內容。因為這兩種都可以,官網示例中只有一個。
if path.ext in (".yaml", ".yml") and path.basename.startswith("test"):
return YamlFile.from_parent(parent, fspath=path)
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測試文件到此結束。