如何标记参数化pytest参数的特定组合?

时间:2017-12-09 15:37:50

标签: python pytest

根据pytest documentation,我可以生成多个参数化参数的组合,如下所示:

@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    pass

我也可以将标记应用于各个参数:

@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.param("6*9", 42,
             marks=pytest.mark.xfail),
])
def test_eval(test_input, expected):
    assert eval(test_input) == expected

是否有合理的方法将两种方法结合起来并将标记应用于特定的参数组合?例如,我是否可以pytest.mark.xfail仅应用test_foox==0生成的y==2实例?

1 个答案:

答案 0 :(得分:3)

我更喜欢的方法是通过简单的辅助函数生成我的参数。看看下面的例子:

import pytest

def __get_param_xy__(x = [], y = [], xfail = []): # ugly demonstrator ...
    out_list = []
    for xx in x: # this could be a clever list comprehension ...
        for yy in y: # this one, too ...
            out_tup = (xx, yy)
            if out_tup in xfail: # the ones you expect to fail
                out_tup = pytest.param(*out_tup, marks = pytest.mark.xfail)
            out_list.append(out_tup)
    return out_list

@pytest.mark.parametrize('x,y', __get_param_xy__(
    x = [0, 1],
    y = [2, 3],
    xfail = [(0, 2)]
    ))
def test_foo(x, y):
    assert not (x == 0 and y == 2)

它仍然使用单个parametrize装饰器,但它非常接近您想要的并且易于阅读和理解。

编辑(1):您实际上可以将辅助函数实现为生成器。以下工作正常:

def __get_param_xy__(x = [], y = [], xfail = []): # ugly generator ...
    for xx in x: # this could be a clever list comprehension ...
        for yy in y: # this one, too ...
            out_tup = (xx, yy)
            if out_tup in xfail: # the ones you expect to fail
                out_tup = pytest.param(*out_tup, marks = pytest.mark.xfail)
            yield out_tup

编辑(2):由于在评论中已经询问过,这实际上可以针对任意数量的参数进行推广,并且不会与灯具冲突。请检查以下示例:

import pytest

class __mock_fixture_class__:
    def __init__(self):
        self.vector = []
    def do_something(self, parameter):
        assert parameter != (0, 2)
        self.vector.append(parameter)
    def fin(self):
        self.vector.clear()

@pytest.fixture(scope = 'function')
def mock_fixture(request):
    mock_fixture_object = __mock_fixture_class__()
    def __finalizer__():
        mock_fixture_object.fin()
    request.addfinalizer(__finalizer__)
    return mock_fixture_object

def __get_param_general_generator__(*_, **kwargs):
    xfail = kwargs.pop('xfail') if 'xfail' in kwargs.keys() else []
    arg_names = sorted(kwargs.keys())
    def _build_args_(in_tup = (), arg_index = 0):
        for val in kwargs[arg_names[arg_index]]:
            out_tup = (*in_tup, val)
            if arg_index < len(arg_names) - 1:
                yield from _build_args_(out_tup, arg_index + 1)
            else:
                if out_tup in xfail:
                    out_tup = pytest.param(*out_tup, marks = pytest.mark.xfail)
                yield out_tup
    return ','.join(arg_names), _build_args_()

@pytest.mark.parametrize(*__get_param_general_generator__(
    x = [0, 1],
    y = [2, 3],
    xfail = [(0, 2)]
    ))
def test_foo_xy(mock_fixture, x, y):
    mock_fixture.do_something((x, y))

@pytest.mark.parametrize(*__get_param_general_generator__(
    x = [0, 1],
    y = [2, 3],
    z = [0, 1, 2],
    xfail = [(0, 2, 1)]
    ))
def test_bar_xyz(x, y, z):
    assert not (x == 0 and y == 2 and z == 1)

(感谢yield from,这是Python 3.3 and above only。)