Search code examples
pythonpytestdynamicparameters

pytest two level of parametrization with one parameter dependent on other one


I have to a test a scenario where one parameter is dependent on other. I tried using the pytest hook pytest_generate_test but i'm not sure how to pass a retrieve the value in hook which is parametrized in the 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}")
    

Output:

============================= 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 ===============================

Expected output:

============================= 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 ===============================

I know I have hard coded to get the first element of the list service = apps[0] i'm not sure how can we get the parameter passed in test can be retrieved in pytest_generate_tests

To keep explain it in simple way

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')]

Solution

  • Unless I missed some point in your question and all you want is a slightly odd product that you have already created in your mock example, then you should IMHO just use it as a helper function to generate the parameters for the parametrize decorator:

    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}")