pytest 两级参数化,参数间相互依赖

0 投票
2 回答
34 浏览
提问于 2025-04-14 17:31

我需要测试一个场景,其中一个参数依赖于另一个参数。我尝试使用pytest的一个钩子函数pytest_generate_test,但我不确定如何在这个钩子中获取测试中传递的参数值。

import pytest
import logging

logger = logging.getLogger(__name__)
apps = ['app1', 'app2', 'app3']

def pytest_generate_tests(metafunc):
    common_services = ['dns', 'dhcp']
    service = apps[0]
    common_services.append(service)
    if "total_services" in metafunc.fixturenames:
       metafunc.parametrize("total_services", common_services)

@pytest.fixture()
def total_services(request):
    return request.param


@pytest.mark.parametrize("app", apps)
def test_example(app, total_services):
    logging.info(f"App: {app}, ServiceName: {total_services}")
    

输出结果:

============================= test session starts ==============================
platform darwin -- Python 3.11.7, pytest-8.0.2, pluggy-1.4.0
rootdir: /private/tmp/test/tests
collected 9 items

test_example.py::test_example[dns-app1] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app1, ServiceName: dns
PASSED                                                                   [ 11%]
test_example.py::test_example[dns-app2] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app2, ServiceName: dns
PASSED                                                                   [ 22%]
test_example.py::test_example[dns-app3] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app3, ServiceName: dns
PASSED                                                                   [ 33%]
test_example.py::test_example[dhcp-app1] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app1, ServiceName: dhcp
PASSED                                                                   [ 44%]
test_example.py::test_example[dhcp-app2] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app2, ServiceName: dhcp
PASSED                                                                   [ 55%]
test_example.py::test_example[dhcp-app3] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app3, ServiceName: dhcp
PASSED                                                                   [ 66%]
test_example.py::test_example[app1-app1] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app1, ServiceName: app1
PASSED                                                                   [ 77%]
test_example.py::test_example[app1-app2] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app2, ServiceName: app1
PASSED                                                                   [ 88%]
test_example.py::test_example[app1-app3] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app3, ServiceName: app1
PASSED                                                                   [100%]

============================== 9 passed in 0.01s ===============================

预期的输出结果:

============================= test session starts ==============================
platform darwin -- Python 3.11.7, pytest-8.0.2, pluggy-1.4.0
rootdir: /private/tmp/test/tests
collected 9 items

test_example.py::test_example[dns-app1] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app1, ServiceName: dns
PASSED                                                                   [ 11%]
test_example.py::test_example[dns-app2] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app2, ServiceName: dns
PASSED                                                                   [ 22%]
test_example.py::test_example[dns-app3] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app3, ServiceName: dns
PASSED                                                                   [ 33%]
test_example.py::test_example[dhcp-app1] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app1, ServiceName: dhcp
PASSED                                                                   [ 44%]
test_example.py::test_example[dhcp-app2] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app2, ServiceName: dhcp
PASSED                                                                   [ 55%]
test_example.py::test_example[dhcp-app3] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app3, ServiceName: dhcp
PASSED                                                                   [ 66%]
test_example.py::test_example[app1-app1] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app1, ServiceName: app1
PASSED                                                                   [ 77%]
test_example.py::test_example[app1-app2] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app2, ServiceName: app2
PASSED                                                                   [ 88%]
test_example.py::test_example[app1-app3] 
-------------------------------- live log call ---------------------------------
INFO     root:test_example.py:21 App: app3, ServiceName: app3
PASSED                                                                   [100%]

============================== 9 passed in 0.01s ===============================

我知道我硬编码了来获取列表中的第一个元素service = apps[0],但我不确定如何在pytest_generate_tests中获取测试中传递的参数。

为了简单明了地解释这个问题:

from itertools import product


apps = ['app1', 'app2', 'app3']

def cartesian_product(app, common_servcies):

    return list(product(app, common_servcies))

for app in apps:
    common_services = ['dns', 'dhcp']
    common_services.append(app)
    print(cartesian_product([app], common_services))

Output: 
$ python3 test_example2.py
[('app1', 'dns'), ('app1', 'dhcp'), ('app1', 'app1')]
[('app2', 'dns'), ('app2', 'dhcp'), ('app2', 'app2')]
[('app3', 'dns'), ('app3', 'dhcp'), ('app3', 'app3')]

2 个回答

0

你几乎自己解决了问题。这是一个可以做到的方法:

import itertools
import logging

import pytest

SERVICES_AND_APPS = []
for app in ["app1", "app2", "app3"]:
    services = ["dns", "dhcp"] + [app]
    SERVICES_AND_APPS.extend(itertools.product(services, [app]))


# BEGIN sort
def custom_key(service_and_app):
    """Ensure sort order of dns, dhcp, app1, app2, app3."""
    custom_order = {
        "dns": "a1",
        "dhcp": "a2",
    }
    service, app = service_and_app
    return custom_order.get(service, service), app


SERVICES_AND_APPS.sort(key=custom_key)
# END sort


@pytest.mark.parametrize("service,app", SERVICES_AND_APPS)
def test_example(service, app):
    logging.info("app=%r, service=%r", app, service)

注意事项

  • BEGIN sortEND sort之间的代码是用来确保顺序的。如果你不在乎顺序(一般来说,你不需要在乎),那么可以把那部分删掉,这样脚本会更简单易懂,也会更短。
  • custom_orderdns翻译成a1,把dhcp翻译成a2,这样就能确保你想要的顺序。
1

除非我在你的问题中漏掉了什么重点,或者你只是想要一个在你示例中看起来有点奇怪的产品,那么在我看来,你可以把它当作一个辅助函数来使用,用来生成parametrize装饰器的参数:

import logging
from itertools import product

import pytest


def app_service_product(apps, common_services):
    return list(product(apps, common_services)) + [[a, a] for a in apps]


@pytest.mark.parametrize(
    "app, service",
    app_service_product(["app1", "app2", "app3"], ["dns", "dhcp"]),
)
def test_example(app, service):
    logging.info(f"App: {app}, ServiceName: {service}")

撰写回答