Python 如何参数化 Pytest 夹具

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42228895/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 21:26:27  来源:igfitidea点击:

How to parametrize a Pytest fixture

pythonpytest

提问by Kurt Peek

Consider the following 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__])

The test test_timelineuses a Pytest fixture, timeline, which itself has the attribute instances. This attribute is iterated over in the test, so that the test only passes if the assertion holds for every instancein timeline.instances.

该测试test_timeline使用 Pytest 固定装置timeline,它本身具有属性instances。此属性在测试中迭代,因此只有当断言对每个instancein 都成立时,测试才会通过timeline.instances

What I actually would like to do, however, is to generate 3 tests, 2 of which should pass and 1 of which would fail. I've tried

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

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

but this leads to

但这导致

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

As I understand it, in Pytest fixtures the function 'becomes' its return value, but this seems to not have happened yet at the time the test is parametrized. How can I set up the test in the desired fashion?

据我了解,在 Pytest 固定装置中,函数“变成”了它的返回值,但是在测试参数化时这似乎还没有发生。如何以所需的方式设置测试?

采纳答案by Kurt Peek

From Using fixtures in pytest.mark.parametrizepytest issue, it would appear that it is currently not possible to use fixtures in pytest.mark.parametrize.

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

回答by imiric

This is actually possible via indirect parametrization.

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

This example does what you want with pytest 3.1.2:

此示例使用 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__])

Also see this similar question.

另请参阅此类似问题

回答by hwjp

instead of indirect parametrization, or my hacky solution below involving inheritance, you can also just use the paramsargument to @pytest.fixture()-- i think this is the simplest solution maybe?

而不是间接参数化,或者我下面涉及继承的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

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

回答by hwjp

The use of indirect parametrization works, but I find the need to use request.paramas a magic, unnamed variable a little awkard.

间接参数化的使用是有效的,但我发现需要使用request.param一个神奇的、未命名的变量有点尴尬。

Here's a pattern I've used. It's awkward in a different way, arguably, but perhaps you'll prefer it too!

这是我使用过的一种模式。可以说,它以不同的方式尴尬,但也许你也会更喜欢它!

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

the timelinefixture depends on another fixture called instances, which has a default vale of [0,0,0], but in the actual test, we use parametrizeto inject a series of different values for instances.

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

the advantage as I see it is that everything has a good name, plus you don't need to pass that indirect=Trueflag.

我认为的优点是一切都有一个好名字,而且你不需要通过那个indirect=True标志。

回答by smarie

This is a confusing topic, because people tend to think that fixtures and parameters are the same thing. They are not and are not even collected in the same phase of the pytest run. By design, parameters are meant to be collected when tests are collected(the list of test nodes is under construction), while fixtures are meant to be executed when test nodes are run(the list of test nodes is fixed and cannot be modified). So fixture contents can not be used to change the number of test nodes - this is the role of parameters. See also my detailed answer here.

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

This is the reason why your question has no solution "as is": you should put the Timelineinstances in a parameter, not in a fixture. Like this:

这就是为什么您的问题没有“原样”解决方案的原因:您应该将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 answermentions another topic that IMHO adds confusion here because it is not directly related to your question. Since it is mentioned, and even accepted as the solution, let me elaborate: indeed in pytest, you can not use a fixture in a @pytest.mark.parametrize. But even if you could that would not change anything.

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

Since this feature is now available in pytest-casesas a beta (I'm the author), you can try yourself. The only way to make your example work even with that feature, is still the same: to remove the list from the fixture. So you end up with:

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

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

Which is the same than previous example with an extra, probably useless, layer. Note: see also this discussion.

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

回答by msudder

Your parametrize paramset is referenceing the function. Perhaps you mean to reference Timeline.instances, not the fixture function /timeline/:

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

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

I think you are looking to add a mark xfail to one set of parameters, perhaps with a reason?

我认为您希望将标记 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