pythonpytestparameterized-unit-test

Indirect parameterization with multiple parametrize decorators in pytest


First of all sorry in advance if I'm doing this wrong, this is my first question asked on stackoverflow. So please let me know if my formulation is off.

So I'm working on a project where I want to unit test a pipeline which calls multiple function in a modular fashion depending on parameters which the user chooses. My goal with this unit test is to check if all possible combinations a user could give, work as intended.

So what I have are lists of all the options certain processes have and I want to use multiple @pytest.mark.parametrize() to iterate over them and create every possible combination.

Because an object is created with these parameters and I want to use this setup for multiple test functions I wanted to build a fixture which takes these parameters (indirect) and returns the object which then should be used in the test functions.

Is this even possible with indirect parameterization?

The simplified setup looks something like this:

PARAMETER1_LIST = ["option 1", "option 2", "option 3"]
PARAMETER2_LIST = ["value 1", "value 2"]

@pytest.fixture
def test_pipeline_class(request):
    pipeline_parameters = []
    for parameter in request.param:
        pipeline_parameters.append(parameter)
    test_pipeline = PipelineClass(pipeline_parameters)
    return test_pipeline


@pytest.mark.parametrize("parameter1", PARAMETER1_LIST, indirect=True)
@pytest.mark.parametrize("parameter2", PARAMETER2_LIST, indirect=True)
def test_pipeline_combinations(parameter1, parameter2, test_pipeline_class):
    print(test_pipeline_class.parameters)
    # Further tests

I'm pretty sure there are multiple things wrong with my code (like the iteration over request.param) but I'm wondering if this is even possible or if there is another way to do it in a more cleaner way.


Solution

  • This will work like this:

    import pytest
    
    
    PARAMETER1_LIST = ["option 1", "option 2", "option 3"]
    PARAMETER2_LIST = ["value 1", "value 2"]
    
    
    class PipelineClass:
        def __init__(self, pipeline_parameters):
            self.parameters = pipeline_parameters
    
    @pytest.fixture
    def pipeline_class(request):  # This name can not start with test as pytest tries to run it as a test 
        pipeline_parameters = []
        for parameter in request.param:
            pipeline_parameters.append(parameter)
        test_pipeline = PipelineClass(pipeline_parameters)
        return test_pipeline
    
    
    # Parameter name should be == fixture name, so you can make this parametrize only once for one fixture for one test
    # Parameters should be passed in [] as the parametrize method waits an iterable as the second argument. 
    # And then it's handled as if it is not in []
    # So this feature is created to use the same fixture but with different parameters for different tests.
    @pytest.mark.parametrize("pipeline_class", [PARAMETER1_LIST], indirect=True)  
    def test_pipeline_combinations(pipeline_class):  
        print(pipeline_class.parameters)
    
    
    @pytest.mark.parametrize("pipeline_class", [PARAMETER2_LIST], indirect=True)
    def test_pipeline_combinations2(pipeline_class):
        print(pipeline_class.parameters)
    

    In the code comments I added explanation about the changes I made.
    So, basically, you can not use one fixture twice in one test.

    If you want to run the test 6 times with the combinations of the two lists: "option 1" + "value 1", "option 1" + "value 2", "option 1" + "value 3", "option 2" + "value 1", "option 2" + "value 2", "option 2" + "value 3" you can do it like this:

    import pytest
    
    
    PARAMETER1_LIST = ["option 1", "option 2", "option 3"]
    PARAMETER2_LIST = ["value 1", "value 2"]
    
    
    class PipelineClass:
        def __init__(self, pipeline_parameters):
            self.parameters = pipeline_parameters
    
    
    @pytest.fixture(scope='function')
    def pipeline_class(request):
        return PipelineClass(request.param)
    
    
    def joined_params():
        for opt in PARAMETER1_LIST:
            for val in PARAMETER2_LIST:
                yield opt, val
    
    
    @pytest.mark.parametrize('pipeline_class', joined_params(), indirect=True)
    def test_pipeline_combinations(pipeline_class):
        print(pipeline_class.parameters)