我正在为一组略有不同的设备开发一个自动化测试套件。我正在使用 pytest 框架。测试驱动设备,并检查其响应如何。
该设备由多个相同的通道组成,因此我想利用 pytest 参数化功能,并为每个通道运行测试。与设备通道的通信非常复杂,因此我将其隐藏在通道装置中(在下面的代码中,它仅返回通道名称,但实际上它创建了一个复杂的对象)。我没有使用 pytest.mark.parametrize,而是制作了一个参数化夹具,为每个通道创建并运行一个对象。
channels = ["left", "right"]
@pytest.fixture(scope = 'function', params = channels)
def channel(request):
id = request.param[0] # TODO: Create channel object here, return ID for now
yield id
def test_channel(channel):
print(channel)
pass
到目前为止一切顺利。但现在我需要根据设置更改频道列表 - 不同的设备可能会公开不同数量的频道及其名称。我需要类似的东西
@pytest.fixture(scope="session")
def channels_list(pytestconfig):
target_device = pytestconfig.getini('target_device')
if target_device == "DeviceA":
return ["left", "right"]
if target_device == "DeviceB":
return ["up", "center", "down"]
我将其作为固定装置,以便利用 pytestconfig 基础设施能够在 INI 文件中定义目标设备,或通过命令行覆盖它。
但问题是:如何使用channels_list灯具来参数化其他灯具(通道)?它说我不能直接使用固定功能。
我终于弄清楚如何根据配置文件或命令行参数进行参数化测试。我还做了更具挑战性的事情:参数化列表由对组成 - 测试中使用的 ID,以及测试参数中显示的人类可读名称。
conftest.py:
def pytest_addoption(parser):
parser.addini('target_device', 'Name of the target board (options: DeviceA, DeviceB)')
def channels_list(target_device):
if target_device == "DeviceA":
return [(1, "left"), (2, "right")]
if target_device == "DeviceB":
return [(3, "up"), (4, "center"), (5, "down")]
def pytest_generate_tests(metafunc):
if "channel_descr" in metafunc.fixturenames:
target_device = metafunc.config.getini("target_device")
metafunc.parametrize("channel_descr", channels_list(target_device), ids=lambda x: x[1])
因此,使用“channel_descr”的所有内容都将使用列表之一进行参数化,具体取决于所选的目标板。
但还有更多。您可以在创建参数列表和参数化测试之间使用固定装置:
class Channel:
def __init__(self, descr):
print(f"Create a new channel object: {descr[1]}")
self.id = descr[0]
self.name = descr[1]
@pytest.fixture(scope = 'function')
def channel(channel_descr):
return Channel(channel_descr)
最后简单使用参数化夹具进行测试:
def test_any(channel):
print(channel.id)
将对所选设备中的所有通道执行此测试。此外,测试代码使用通道 ID,而通道名称用作参数的人类可读名称。
test_test.py::test_any[left] PASSED
test_test.py::test_any[right] PASSED
元函数还可以用于根据运行时条件禁用某些测试。
conftest.py:
def is_good_device(target_device):
return target_device in ["DeviceA"]
def pytest_collection_modifyitems(config, items):
target_device = config.getini("target_device")
for item in items:
if "good_device" in item.keywords and not is_good_device(target_device):
item.add_marker(pytest.mark.skip(reason="Not a good device"))
测试:
@pytest.mark.good_device
def test_good_device(channel):
print(channel.id)
此方法与标准方法的区别在于,条件是在运行时计算的,并且“is_good_device”函数可能非常重要。
test_test.py::test_any[up] PASSED [ 16%]
test_test.py::test_any[center] PASSED [ 33%]
test_test.py::test_any[down] PASSED [ 50%]
test_test.py::test_good_device[up] SKIPPED (Not a good device) [ 66%]
test_test.py::test_good_device[center] SKIPPED (Not a good device) [ 83%]
test_test.py::test_good_device[down] SKIPPED (Not a good device) [100%]