endolith
endolith

Reputation: 26823

How to parametrize different numbers or positions of arguments?

For example, say I have two functions:

def func3(a, b, c):
    for var in (a, b, c):
        if var < 0:
            raise ValueError
    pass


def func7(a, b, c, d, e, f, g):
    for var in (a, b, c, d, e, f, g):
        if var < 0:
            raise ValueError
    pass

And in my tests, I want to test a set of invalid values at each parameter. The only way I know of to do this is to write them all out:

@pytest.mark.parametrize('val', [-2, -3, -4.5])
def test_invalid_param(val):
    with pytest.raises(ValueError):
        func3(val, 0, 0)
    with pytest.raises(ValueError):
        func3(0, val, 0)
    with pytest.raises(ValueError):
        func3(0, 0, val)
    with pytest.raises(ValueError):
        func7(val, 0, 0, 0, 0, 0, 0)
    with pytest.raises(ValueError):
        func7(0, val, 0, 0, 0, 0, 0)
    with pytest.raises(ValueError):
        func7(0, 0, val, 0, 0, 0, 0)
    ...

How can I combine these all into one with pytest.raises case?

If both funcs had the same number of arguments, I could stack this:

@pytest.mark.parametrize('func', [func3, func7])

but they don't. If they just had different numbers of arguments and I was only testing the first, then I could stack this:

@pytest.mark.parametrize('func, args', [(func3, (0, 0)), 
                                        (func7, (0, 0, 0, 0, 0, 0))])

But that won't work for testing the parametrized value in multiple positions.

Upvotes: 0

Views: 347

Answers (1)

Guy
Guy

Reputation: 50899

If you don't mind increasing the number of tests you can use the values sets as a parameter to the test. Each set will be a different test

def data_source():
    for val in [-2, -3, -4.5]:
        for values_set in [[val, 0, 0], [0, val, 0], [0, 0, val], [val, 0, 0, 0, 0, 0, 0], [0, val, 0, 0, 0, 0, 0], [0, 0, val, 0, 0, 0, 0]]:
            yield values_set


def func(values):
for var in values:
    if var < 0:
        raise ValueError
pass


@pytest.mark.parametrize('values', data_source())
def test_invalid_param(values):
    with pytest.raises(ValueError):
        func(values)

In case of failure the stack trace will look like

(test_invalid_param[values6])
values = [3, 0, 0]

    @pytest.mark.parametrize('values', data_source())
    def test_invalid_param(values):
        with pytest.raises(ValueError):
>           func(*values)
E           Failed: DID NOT RAISE <class 'ValueError'>

Example_test.py:32: Failed

As a side note, to allow different number of arguments you can use * in the parameter

def func(*values):
    for var in values:
        if var < 0:
            raise ValueError
    pass

Upvotes: 2

Related Questions