Parametrizing fixtures and functions without combinations

Question:

Here’s the thing:

I have an Array2D class that receives parameters ‘shape’ and ‘val’. The constructor is as follows:

class Array2D:
    ...
    def __init__(self, shape: tuple, val):
        self.shape = shape
        self.size = self.shape[0] * self.shape[1]
        self.data = [val] * self.size
    ...

I would like to perform tests on this class. To do this I have declared a variable ARRAY_CONFIG, which lists parameters for different two-dimensional arrays. Here is an example:

ARRAY_CONFIG = [
    [(2, 3), 3],
    [(2, 3), 1.7]
]

I have also defined other lists that store the expected values for future tests. Take a look:

SIZE = [6, 6]
DATA = [
    [3, 3, 3, 3, 3, 3],
    [1.7, 1.7, 1.7, 1.7, 1.7, 1.7]
]

Now I want to build a test function that by the parameterization decorator, makes comparison between these lists. Here is an example without the decorators from the PyTest library:

def test_size_and_data():
    for i in range(len(ARRAY_CONFIG)):
        array_config = ARRAY_CONFIG[i]
        size = SIZE[i]
        data = DATA[i]

        array = Array2D(*array_config)
        assert array.size == size
        assert array.data == data

I managed to develop something, but it seems somewhat repetitive to me. Here it is:

def combine(list1, list2):
    return [(list1[i], list2[i]) for i in range(len(list1))]


@pytest.fixture
def array(request):
    return Array2D(*request.param)


@pytest.mark.parametrize('array, expected', combine(ARRAY_CONFIG, SIZE), indirect=['array'])
def test_size(array, expected):
    assert array.size == expected


@pytest.mark.parametrize('array, expected', combine(ARRAY_CONFIG, DATA), indirect=['array'])
def test_data(array, expected):
    assert array.data == expected

What is the best way to perform this kind of test where there is a list of "expected values"? I’ve tried performing two parameterizations, but I end up performing combinations between the values, rather than one-to-one comparisons. As done here:

# Other tests --> generate 4 tests instead of 2
@pytest.fixture(params=ARRAY_CONFIG)
def array(request):
    return Array2D(*request.param)


@pytest.fixture
def array_size(array):
    return array.size


@pytest.mark.parametrize('expected', SIZE)
def test_size(array_size, expected):
    assert array_size == expected

Thanks in advance.

Asked By: gusta

||

Answers:

It’s much simpler if you use the combine function as the parameter in parametrize. You can yield every set of values

def combine():
    for arr, size, data in zip(ARRAY_CONFIG, SIZE, DATA):
        yield Array2D(*arr), size, data


@pytest.mark.parametrize('expected', combine())
def test_size_and_data(expected):
    array, size, data = expected
    print(array.size, size)
    print(array.data, data)

Or more explicit parameters

@pytest.mark.parametrize('array, size, data', combine())
def test_size_and_data(array, size, data):
    print(array.size, size)
    print(array.data, data)

Output

example_test.py::test_size_and_data[expected0] PASSED                    [ 50%]
6 6
[3, 3, 3, 3, 3, 3] [3, 3, 3, 3, 3, 3]

example_test.py::test_size_and_data[expected1] PASSED                    [100%]
6 6
[1.7, 1.7, 1.7, 1.7, 1.7, 1.7] [1.7, 1.7, 1.7, 1.7, 1.7, 1.7]

--------------------------------------------------------------

example_test.py::test_size_and_data[array0-6-data0] PASSED               [ 50%]
6 6
[3, 3, 3, 3, 3, 3] [3, 3, 3, 3, 3, 3]

example_test.py::test_size_and_data[array1-6-data1] PASSED               [100%]
6 6
[1.7, 1.7, 1.7, 1.7, 1.7, 1.7] [1.7, 1.7, 1.7, 1.7, 1.7, 1.7]
Answered By: Guy