如何将参数传递给pytest的fixture?

65
我所有测试的基线是,出租车上始终会有至少一名乘客。我可以通过一些基本的装置轻松实现这个设置:
from blah import Passenger, Taxi

@pytest.fixture
def passenger():
    return Passenger()

@pytest.fixture
def taxi(passenger):
    return Taxi(rear_seat=passenger)

测试基线很简单:
def test_taxi_contains_passenger(taxi)
    assert taxi.has_passenger()

我的问题出现在我开始需要更复杂的测试设置时。有些情况下,我需要出租车有多个乘客,有些情况下我需要定义乘客的属性。例如:
def test_three_passengers_in_taxi(taxi)
    assert taxi.has_passengers(3)
    assert taxi.front_passenger_is_not_a_child()

我可以通过为特定的测试创建特定的装置来解决这个问题。对于上述测试,我会创建以下装置:
@pytest.fixture
def three_passenger_test_setup(taxi)
    taxi.add_front_seat_passenger(Passenger(child=False))
    taxi.add_rear_seat_passenger(Passenger())
    return taxi

我可以将上述的fixture传递到我的测试用例中,一切都很好,但是如果我选择这种方法,可能会为每个测试都得到一个fixture,感觉应该有更高效的方法来做这件事。
有没有办法传递参数给fixture,以便这些参数可以用于创建fixture返回的对象?我应该给测试函数参数化吗?还是fixture?或者我是在浪费时间,每个测试都需要一个fixture?
5个回答

111

我们可以通过使用一种方法,在夹具(fixture)中传递参数并返回该方法来实现这一点。

让我给你举个例子。

@pytest.fixture
def my_fixture():

  def _method(a, b):
    return a*b

  return _method

def test_me(my_fixture):
  result1 = my_fixture(2, 3)
  assert result1 == 6

  result2 = my_fixture(4, 5)
  assert result2 == 20

7
我喜欢这个东西,它非常实用。 - Tommy
4
你能否再解释一下?如果my_fixture()函数不接受任何参数,为什么我可以传递参数给它呢?此外,如果我在测试用例中使用多个fixture,但只想向其中一个fixture传递参数,会发生什么? - Jacob Pavlock
11
这样想。当你将夹具函数my_fixture作为参数传递给test_me时,它会被调用。返回的值存储在my_fixture参数中。因此,在test_me中调用my_fixture时,实际上是在调用返回的值。 - nightgaunt
这节省了我很多时间。谢谢! - Amogh Mishra
2
@supamaze,如果我想让我的装置具有设置和拆卸部分,这将如何工作?在拆卸步骤之前在内部函数中放置yield是不起作用的。 - Amit Tendulkar
2
@AmitTendulkar -- 在内部函数中使用 return,然后在 fixture 中 yield 内部函数,最后添加拆卸代码。 - Alexandru Dinu

17
你可以使用测试参数化与indirect=True。在pytest文档中:Apply indirect on particular arguments。如下所示:https://dev59.com/6mMl5IYBdhLWcg3w7qlq#33879151
使用一些指定参数的 fixture 还是另一个选项,可能更适合您:
@pytest.fixture(params=[3,4])
def number_of_passengers(request):
    return request.param

然后从出租车和测试本身访问此装置:

@pytest.fixture
def taxi(number_of_passengers):
    return Taxi(rear_seat=Passenger() * number_of_passengers)

def test_three_passengers_in_taxi(taxi, number_of_passengers)
    assert taxi.has_passengers(number_of_passengers)
    assert taxi.front_passenger_is_not_a_child()

如果你的测试用例和断言在不同的测试中非常相似,那么这种方法是不错的。


还是我在浪费时间,每个测试用例都需要一个固定装置吗?

我觉得你绝对不应该为每个测试函数创建一个固定装置。相反,你可以把设置放在测试内部。仅当你必须为出租车的不同情况进行不同断言时,这实际上是一种可行的替代方法。


最后,你可以使用另一种可能的模式,即出租车工厂。虽然对于你提供的示例来说并不太有用,但如果需要多个参数且只有一些参数发生变化,则可以创建类似以下代码的装置:

from functools import partial
@pytest.fixture
def taxi_factory():
    return partial(Taxi, 1, 2, 3)

8

简而言之,使用pytest.markrequest fixture来访问request.keywords

这是一个非常古老的问题,但现有的答案对我并不起作用,因此这里是我的解决方案,使用pytest.mark。

from blah import Passenger, Taxi

@pytest.fixture
def passenger():
    return Passenger()

@pytest.fixture
def taxi(passenger, request):
    if "taxi" in request.keywords:
        kwargs = request.keywords["taxi"].kwargs
    else:
        kwargs = dict(rear_seat=passenger)
    return Taxi(**kwargs)


# This allows testing the baseline as-is...

def test_taxi_contains_passenger(taxi)
    assert taxi.has_passenger()

# and also using pytest.mark to pass whatever kwargs:

@pytest.mark.taxi(rear_seat=[Passenger()] * 3)
def test_three_passengers_in_taxi(taxi)
    assert taxi.has_passengers(3)
    assert taxi.front_passenger_is_not_a_child()


太棒了!这对我起作用了,它以简洁明了的方式声明了测试! - Yakovlev Pavel
这种方法比被接受的答案更直观。效果很好。 - undefined

8
那个fixture只是一个Python装饰器。
@decorator
def function(args):
  ...

是“花哨”的意思。
def function(args):
  ...
function = decorator(function)

因此,您可能会编写自己的装饰器,将您想要修饰的函数和fixture封装在您需要的任何东西中。

def myFixture(parameter):
  def wrapper(function):
    def wrapped(*args, **kwargs):
      return function(parameter, *args, **kwargs)
    return wrapped
  return pytest.fixture(wrapper)

@myFixture('foo')
def function(parameter, ...):
  ...

这将像fixture一样运作,但会将一个值('foo')作为参数传递给函数


0
例如,您可以向具有嵌套函数core()addition()夹具传递2个参数,如下所示:
import pytest

@pytest.fixture
def addition():
    def core(num1, num2):
        return num1 + num2
    return core

def test(request, addition):
    print(addition(2, 3))
    print(request.getfixturevalue("addition")(6, 8))
    assert True

输出:

$ pytest -q -rP
.                                [100%]
=============== PASSES ================ 
________________ test _________________ 
-------- Captured stdout call --------- 
5
14
1 passed in 0.10s

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接