如何参数化Pytest夹具

时间:2017-02-14 14:37:52

标签: python pytest

考虑以下Pytest:

import pytest

class TimeLine(object):
    instances = [0, 1, 2]

@pytest.fixture
def timeline():
    return TimeLine()

def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

if __name__ == "__main__":
    pytest.main([__file__])

测试test_timeline使用Pytest fixture timeline,它本身具有属性instances。此属性在测试中迭代,因此只有在instance中每个timeline.instances的断言成立时,测试才会通过。

然而,我真正想要做的是生成3个测试,其中2个应该通过,其中1个将失败。我试过了

@pytest.mark.parametrize("instance", timeline.instances)
def test_timeline(timeline):
    assert instance % 2 == 0

但这会导致

AttributeError: 'function' object has no attribute 'instances'

据我了解,在Pytest中,函数'变为'它的返回值,但在测试参数化时似乎还没有发生。如何以理想的方式设置测试?

7 个答案:

答案 0 :(得分:28)

这实际上可以通过indirect parametrization

此示例使用pytest 3.1.2执行您想要的操作:

import pytest

class TimeLine:
    def __init__(self, instances):
        self.instances = instances

@pytest.fixture
def timeline(request):
    return TimeLine(request.param)

@pytest.mark.parametrize(
    'timeline',
    ([1, 2, 3], [2, 4, 6], [6, 8, 10]),
    indirect=True
)
def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

if __name__ == "__main__":
    pytest.main([__file__])

另见this similar question

答案 1 :(得分:11)

除了间接参数化或我下面涉及继承的hacky解决方案之外,您还可以仅将params的参数@pytest.fixture()使用-我认为这也许是最简单的解决方案?

import pytest

class TimeLine:
    def __init__(self, instances=[0, 0, 0]):
        self.instances = instances


@pytest.fixture(params=[
    [1, 2, 3], [2, 4, 5], [6, 8, 10]
])
def timeline(request):
    return TimeLine(request.param)


def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

https://docs.pytest.org/en/latest/fixture.html#parametrizing-fixtures

答案 2 :(得分:5)

https://bitbucket.org/pytest-dev/pytest/issues/349/using-fixtures-in-pytestmarkparametrize开始,似乎无法在pytest.mark.parametrize中使用灯具。

答案 3 :(得分:4)

使用间接参数化是可行的,但是我发现有必要使用request.param作为一个魔术的,未命名的变量,有点麻烦。

这是我使用的模式。可以说,它以不同的方式很尴尬,但是也许您也会喜欢它!

import pytest

class TimeLine:
    def __init__(self, instances):
        self.instances = instances


@pytest.fixture
def instances():
    return [0, 0, 0]


@pytest.fixture
def timeline(instances):
    return TimeLine(instances)


@pytest.mark.parametrize('instances', [
    [1, 2, 3], [2, 4, 5], [6, 8, 10]
])
def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

timeline固定装置依赖于另一个称为instances的固定装置,其默认值为[0,0,0],但在实际测试中,我们使用parametrize注入一系列instances的不同值。

我所看到的优点是,每个东西都有一个好名字,而且您不需要传递那个indirect=True标志。

答案 4 :(得分:0)

这是一个令人困惑的话题,因为人们倾向于认为固定装置和参数是同一回事。它们不是,甚至在pytest运行的同一阶段也不收集。按照设计,参数是在收集(正在构建测试节点的列表)时收集的,而夹具是在测试节点运行时执行的(测试节点列表是固定的,不能修改)。因此,夹具内容不能用于更改测试节点的数量-这是参数的作用。另请参阅我的detailed answer here

这就是您的问题没有“按原样”解决方案的原因:您应该将Timeline实例放置在参数中,而不是夹具中。像这样:

import pytest

class TimeLine(object):
    instances = [0, 1, 2]

@pytest.fixture(params=TimeLine().instances)
def timeline(request):
    return request.param

def test_timeline(timeline):
    assert timeline % 2 == 0

Kurt Peek's answer提到了恕我直言,在此添加了另一个主题,因为它与您的问题没有直接关系。既然提到了它,甚至被接受为解决方案,那么让我详细说明一下:实际上,在pytest中,您不能在@pytest.mark.parametrize中使用固定装置。但是即使可以,也不会改变任何东西。

由于此功能现已在pytest-cases中作为Beta版提供(我是作者),因此您可以尝试一下。使示例即使具有该功能也可以工作的唯一方法仍然是相同的:从灯具中删除列表。因此,您最终得到:

import pytest
from pytest_cases import pytest_parametrize_plus, fixture_ref

class TimeLine(object):
    instances = [0, 1, 2]

@pytest.fixture(params=TimeLine().instances)
def timeline(request):
    return request.param

@pytest_parametrize_plus("t", [fixture_ref(timeline)])
def test_timeline(t):
    assert t % 2 == 0

与前面的示例相同,但有一个额外的层,可能没有用。 注意:另请参见this discussion

答案 5 :(得分:0)

您正在参数化参数集正在引用该函数。也许您的意思是引用Timeline.instances,而不是Fixture函数/ timeline /:

@pytest.mark.parametrize("instance", Timeline.instances)
def test_func(instance):
    pass

我认为您正在寻找将xfail标记添加到一组参数的方法,也许是有原因的吗?

def make_my_tests():
    return [0, 2, mark.xfail(1, reason='not even')]

@mark.parametrize('timeline', paramset=make_my_tests(), indirect=True)
def test_func(timeline):
    assert timeline % 2 == 0

答案 6 :(得分:0)

我解决了创建一个没有fixture装饰器的函数和一个应用装饰器的先前函数的问题:

def one_raw():
    return 1

one = pytest.fixture(one_raw)

def test_add(one):
    assert one + 2 == 3

@pytest.mark.parametrize("subtrahend", (one_raw(), int(True)))
def test_sub(subtrahend):
    assert 4 - subtrahend == 3