how to pass multiple list arguments by command line to multiple pytest fixtures

Viewed 1206

I want to pass below lists from my command line to my pytest code (like argparser in python), can anyone help me how to do this ?

pytest -vs test_sample.py --html=results.html --A_list=['A1', 'A2'] --B_list=['B1'] --C_list=['C1', 'C2']

Here is my pytest code.

import pytest

@pytest.fixture(scope="session", params=A_list) # here A_list is a list and it has to be passed from command line
def get_A_list(request):
    print("inside fixture get_A_list - element in A_list: ".format(request.param))
    yield request.param


@pytest.fixture(scope="module", params=B_list) # here B_list is a list and it has to be passed from command line
def get_B_list(request, get_A_list):
    print("inside fixture get_B_list - element in A_list: {}, element in B_list: {}: ".format(get_A_list, request.param))
    yield request.param


@pytest.mark.parametrize("c", C_list) # here C_list is a list and it has to be passed from command line
def test_functionality(get_A_list, get_B_list, c):
    print("inside function test_functionality - element in A_list: {}, element in B_list: {}, element in C_list: {}: ".format(get_A_list, get_B_list, c))

test_functionality is my test case function. The number of test cases should be based on length of lists passed in command line

It has to execute test cases in below order for the above mentioned lists.

test_functionality[A1-B1-C1]
test_functionality[A1-B1-C2]
test_functionality[A2-B1-C1]
test_functionality[A2-B1-C2]

I need to perform some operation in **fixture get_A_list using elements in A_list ....similarly i need to perform some operations in fixture get_B_list using elements in A_list and B_list .......like this i have 4 fixtures and finally i need to execute the test cases in test_functionality which will use elements in A_list , B_list , C_list etc..

Finally the the output should be like below ..

    inside fixture get_A_list - element in A_list: A1
    inside fixture get_B_list - element in A_list: A1, element in B_list: B1
    inside function test_functionality - element in A_list: A1, element in B_list: B1, element in C_list: C1
    inside function test_functionality - element in A_list: A1, element in B_list: B1, element in C_list: C2
    inside function test_functionality - element in A_list: A1, element in B_list: B1, element in C_list: C3
    inside fixture get_A_list - element in A_list: A2
    inside fixture get_B_list - element in A_list: A2, element in B_list: B1
    inside function test_functionality - element in A_list: A2, element in B_list: B1, element in C_list: C1
    inside function test_functionality - element in A_list: A2, element in B_list: B1, element in C_list: C2
    inside function test_functionality - element in A_list: A2, element in B_list: B1, element in C_list: C3
1 Answers

The problem here is that the params argument is read at load time, where the information about command line parameters is not yet available, so you cannot just set them like in your example.

The best way is proably to restructure your code so that would not be needed. If you are able to do that so you don't need the additional fixtures,

Anyway, you first have to register your command line arguments:

conftest.py

def pytest_addoption(parser):
    parser.addoption("--A_list", action="store")
    parser.addoption("--B_list", action="store")
    parser.addoption("--C_list", action="store")

Now, if you can restructure your code so that the fixtures do not depend on the parameters, you can parametrize your tests based on the passed command line arguments. Provided you pass the arguments as comma-separated lists, you can do something like this:

test.py

import pytest

@pytest.hookimpl
def pytest_generate_tests(metafunc):
    # collect fixture names with values
    option_values = {
        "a": metafunc.config.option.A_list,
        "b": metafunc.config.option.B_list,
        "c": metafunc.config.option.C_list
    }

    # add parametrization for each fixture name
    for name, value in option_values.items():
        if name in metafunc.fixturenames and value is not None:
            metafunc.parametrize(name, value.split(","))

def test_functionality1(a, b, c):
    print(a, b, c)

This will create the parametrized tests in the desired order.

If that is not possibble, you may try to create your fixtures at runtime instead (see this post for more information):

conftest.py

# define the functions used as fixtures
def a_list_fixture(request):
    print(f"get_A_list: {request.param}")
    yield request.param

def b_list_fixture(request, get_A_list):
    print(f"get_B_list: {get_A_list},  {request.param}")
    yield request.param

# at session start, create the fixtures with the correct params
def pytest_sessionstart(session):
    a_list = session.config.option.A_list
    if a_list:
        a_list = a_list.split(",")
        name = "get_A_list"
        fn = pytest.fixture(scope='session', name=name,
                            params=a_list)(a_list_fixture)
        setattr(sys.modules[__name__], "{}_func".format(name), fn)
    b_list = session.config.option.B_list
    if b_list:
        b_list = b_list.split(",")
        name = "get_B_list"
        fn = pytest.fixture(scope='session', name=name,
                            params=b_list)(b_list_fixture)
        setattr(sys.modules[__name__], "{}_func".format(name), fn)

Note that you still need to parametrize the function for c as above:

test.py

import pytest

@pytest.hookimpl
def pytest_generate_tests(metafunc):
    c_value = metafunc.config.option.C_list
    if "c" in metafunc.fixturenames and value is not None:
            metafunc.parametrize("c", value.split(","))

def test_functionality(get_A_list, get_B_list, c):
    print(f"test_functionality {get_A_list}, {get_B_list}, {c}")

This creates the needed parametrized tests:

pytest -vv --A_list="A1,A2" --B_list="B1" --C_list="C1,C2":

================================================= test session starts =================================================
...
collected 4 items

so/cmd_line_args/test.py::test_functionality[C1-A1-B1] PASSED                                                    [ 25%]
so/cmd_line_args/test.py::test_functionality[C2-A1-B1] PASSED                                                    [ 50%]
so/cmd_line_args/test.py::test_functionality[C1-A2-B1] PASSED                                                    [ 75%]
so/cmd_line_args/test.py::test_functionality[C2-A2-B1] PASSED                                                    [100%]

================================================== 4 passed in 0.28s ==================================================

Note that the parameter order is not as desired, but this is merely a cosmetic issue. Here is the output of the print functions:

pytest -s --A_list="A1,A2" --B_list="B1" --C_list="C1,C2"

================================================= test session starts =================================================
...
collected 4 items

test.py get_A_list: A1
get_B_list: A1,  B1
test_functionality A1, B1, C1
.test_functionality A1, B1, C2
.get_A_list: A2
get_B_list: A2,  B1
test_functionality A2, B1, C1
.test_functionality A2, B1, C2
.

================================================== 4 passed in 0.27s ==================================================
Related