Warm tip: This article is reproduced from stackoverflow.com, please click
pytest python

Py.test: How to parametrize when some values should return an error

发布于 2020-03-27 10:16:18

I'm new at testing and wondering if it is possible to parametrize values that should return a value, and others that should return an error to a single test.

Let's say I have a simple function divide_hundred_by(x), defined as follows:

def divide_hundred_by(x):
    if x == 0: 
         raise ZeroDivisionError('You cannot divide by zero') 
    return 100/x

Now, I'd like to test this function for a couple of values for x and parametrize this test. I found that I can use:

import pytest

@pytest.mark.parametrize('value, expected',
                         [
                             (10, 10),
                             (-2, -50),
                             (0.5, 200)
                         ]
                         )
def test_divide_hundred_by(value, expected):

    with pytest.raises(ZeroDivisionError): 
        divide_hundred_by(0)

    assert divide_hundred_by(value) == expected

But this ensures that if the warning-part fails, the entire test fails for all values, which is not what I'd like.

I'm wondering if it is possible to write something of the form:

@pytest.mark.parametrize('value, expected',
                         [
                             (10, 10),
                             (-2, -50),
                             (0.5, 200),
                             (0, "ZeroDivisionError") 
                         ]
                         )
def test_divide_hundred_by(value, expected):
    assert divide_hundred_by(value) == expected

such that the test will pass for the other parameters. I cannot find anything online on the matter.

Questioner
D.C.
Viewed
158
AKX 2019-07-03 21:19

How about this – you can check the type of expected and if it smells like an exception class, use pytest.raises() instead:

import pytest


def divide_hundred_by(x):
    if x == 0:
        raise ZeroDivisionError("You cannot divide by zero")
    return 100 / x


@pytest.mark.parametrize(
    "value, expected",
    [
        (10, 10),
        (-2, -50),
        (0.5, 200),
        (0, ZeroDivisionError),
    ],
)
def test_divide_hundred_by(value, expected):
    if type(expected) == type and issubclass(expected, Exception):
        with pytest.raises(expected):
            divide_hundred_by(value)
    else:
        assert divide_hundred_by(value) == expected

If you have more of this sort of thing, you can refactor the if/with/else bit into a helper function:

import pytest


def divide_hundred_by(x):
    if x == 0:
        raise ZeroDivisionError("You cannot divide by zero")
    return 100 / x


def check(fn, expected, args=(), kwargs={}):
    if type(expected) == type and issubclass(expected, Exception):
        with pytest.raises(expected):
            fn(*args, **kwargs)
    else:
        assert fn(*args, **kwargs) == expected


@pytest.mark.parametrize(
    "value, expected",
    [(10, 10), (-2, -50), (0.5, 200), (0, ZeroDivisionError)],
)
def test_divide_hundred_by(value, expected):
    check(divide_hundred_by, expected, (value,))