如何参数化Pytest夹具

Kur*_*eek 31 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__])
Run Code Online (Sandbox Code Playgroud)

该测试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
Run Code Online (Sandbox Code Playgroud)

但这会导致

AttributeError: 'function' object has no attribute 'instances'
Run Code Online (Sandbox Code Playgroud)

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

imi*_*ric 31

这实际上可以通过间接参数化来实现.

这个例子用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__])
Run Code Online (Sandbox Code Playgroud)

另见这个类似的问题.

  • 您的测试失败,并显示“FAILED indir-param.py::test_timeline[timeline0] - assert (1 % 2) == 0”:-)(我知道这对答案并不重要)。 (4认同)
  • 是的,它应该失败。1 除以 2 的余数(模数)不为 0。 (2认同)

sma*_*rie 18

这是一个令人困惑的话题,因为人们往往认为灯具和参数是一回事。它们不是,甚至不会在 pytest 运行的同一阶段收集。按照设计,参数是在收集测试时收集的(测试节点列表正在构建中),而夹具则是在测试节点运行执行(测试节点列表是固定的,不能修改)。所以夹具内容不能用来改变测试节点的数量——这就是参数的作用。另请参阅我的详细答案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
Run Code Online (Sandbox Code Playgroud)

Kurt Peek 的回答提到了另一个主题,恕我直言在这里增加了混淆,因为它与您的问题没有直接关系。既然它被提及,甚至被接受为解决方案,让我详细说明一下:确实在 pytest 中,您不能在@pytest.mark.parametrize. 但即使你能做到,也不会改变任何事情。

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

import pytest
from pytest_cases import parametrize, fixture_ref

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

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

@parametrize("t", [fixture_ref(timeline)])
def test_timeline(t):
    assert t % 2 == 0
Run Code Online (Sandbox Code Playgroud)

这与前面的示例相同,但有一个额外的,可能没用的层。注意:另请参阅此讨论


Kur*_*eek 15

Using fixtures in pytest.mark.parametrize pytest issue 来看,目前似乎无法在pytest.mark.parametrize.

  • 这是不正确的,参数化标记接受indirect=True 或indirect=['specific', 'named'] 来说明哪些参数将调用相应的装置。 (2认同)
  • 正如@msudder所评论的,查找[间接参数化](https://docs.pytest.org/en/stable/example/parametrize.html#indirect-parametrization) (2认同)

hwj*_*wjp 12

而不是间接参数化,或者下面涉及继承的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
Run Code Online (Sandbox Code Playgroud)

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


Pit*_*kos 7

香草 pytest 是可能的

测试数字.py

import pytest


@pytest.fixture(params=[1, 2, 3, 4, 5])
def number(request):
    yield request.param


def test_number_is_integer(number):
    assert isinstance(number, int)
Run Code Online (Sandbox Code Playgroud)

输出

test_numbers.py::test_number_is_integer[1] PASSED
test_numbers.py::test_number_is_integer[2] PASSED
test_numbers.py::test_number_is_integer[3] PASSED
test_numbers.py::test_number_is_integer[4] PASSED
test_numbers.py::test_number_is_integer[5] PASSED
Run Code Online (Sandbox Code Playgroud)


hwj*_*wjp 6

间接参数化的使用是可行的,但是我发现有必要使用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
Run Code Online (Sandbox Code Playgroud)

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

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