如何参数化 Pytest 夹具
Posted
技术标签:
【中文标题】如何参数化 Pytest 夹具【英文标题】:How to parametrize a Pytest fixture 【发布时间】:2017-07-02 21:42:52 【问题描述】:考虑以下 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 夹具timeline
,它本身具有instances
属性。该属性在测试中被迭代,因此只有当断言对timeline.instances
中的每个instance
都成立时,测试才会通过。
然而,我实际上想做的是生成 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 固定装置中,函数“成为”它的返回值,但这似乎在测试参数化时还没有发生。如何以所需的方式设置测试?
【问题讨论】:
这能回答你的问题吗? Pass a parameter to a fixture function 【参考方案1】:这实际上可以通过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。
【讨论】:
您的测试失败,FAILED indir-param.py::test_timeline[timeline0] - assert (1 % 2) == 0
:-)(我知道答案并不重要)。【参考方案2】:
除了间接参数化,或者我下面涉及继承的 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/how-to/fixtures.html#parametrizing-fixtures
【讨论】:
【参考方案3】:这是一个令人困惑的话题,因为人们倾向于认为夹具和参数是一回事。它们不会,甚至不会在 pytest 运行的同一阶段收集。按照设计,参数是在收集测试时收集的(测试节点列表正在构建中),而夹具是在测试节点运行时执行的(测试节点列表是固定的,不能修改)。所以fixture内容不能用来改变测试节点的数量——这是参数的作用。另请参阅我的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
中作为测试版提供(我是作者),您可以自己尝试。即使使用该功能,使您的示例工作的唯一方法仍然是相同的:从夹具中删除列表。所以你最终得到:
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
这与前面的示例相同,但多了一个可能无用的层。 注意:另见this discussion。
【讨论】:
【参考方案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
fixture 依赖于另一个名为instances
的fixture,它的默认值为[0,0,0]
,但在实际测试中,我们使用parametrize
为instances
注入了一系列不同的值。
在我看来,它的优点是所有东西都有一个好名字,而且你不需要传递 indirect=True
标志。
【讨论】:
【参考方案5】:从 Using fixtures in pytest.mark.parametrize pytest 问题来看,目前似乎无法在 pytest.mark.parametrize
中使用固定装置。
【讨论】:
这是不正确的,参数化标记接受indirect=True 或indirect=['specific', 'named'] 来说明哪些参数将调用各自的fixture。 您帖子中引用的提案已移至 GitHub,网址为 github.com/pytest-dev/pytest/issues/349。 正如@msudder 评论的那样,查找indirect parameterization【参考方案6】:您的参数化参数集正在引用该函数。也许您的意思是引用 Timeline.instances,而不是夹具函数 /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
【讨论】:
以上是关于如何参数化 Pytest 夹具的主要内容,如果未能解决你的问题,请参考以下文章
pytest自动化测试框架详解+mark标记+fixture夹具