【问题标题】:How can I pass fixtures to pytest.mark.parameterize?如何将夹具传递给 pytest.mark.parametrize?
【发布时间】:2021-09-21 17:41:38
【问题描述】:

我正在尝试将三个不同的装置传递给我的 pytest.mark.parameterize 装饰器,如下所示:

@pytest.mark.parametrize("credentials, return_code", [
    (user1, 200),
    #(user2, 200),
    #(user3, 401)
])
def test_login():
    assert True
# Actual test uses response.return_code == return_code

但是,我收到一条错误消息:

NameError: name 'user1' is not defined

这不正确,我在conftest.py中定义了它:

user1_info = [('user1', 'password')]
@pytest.fixture(params=user1_info)
def user1(request):
    return request.param

如果我将测试更改为这样,夹具就可以工作:

def test_login(user1):
    assert True
# Actual test uses response.return_code == return_code

问题是,如果我这样做,我必须编写三个单独的测试,一个用于每个夹具,一个用于每个 status_code 检查。如果我可以使用parameterize,我可以使用一个函数并检查用户/预期的返回码。

我如何利用我的固定装置是pytest.mark.parameterize 装饰器的一部分?

【问题讨论】:

    标签: python pytest


    【解决方案1】:

    要将夹具作为参数化参数传递,请将它们作为字符串引用并使用关键字参数indirect=['arg_name', ...]

    然后使用参数名称credentialsreturn_code 作为测试函数的参数。

    @pytest.mark.parametrize(
        "credentials, return_code",
        [
            ("user1", 200),
            ("user2", 200),
            ("user3", 401),
        ],
        # Only the `credentials` parameter is a reference to the
        # fixture. The `return_code` should be passed as is.
        indirect=["credentials"],
    )
    def test_login(credentials, return_code):
        return True
    

    pytest 的 parametrize 魔术和 indirect 的文档可以在这里找到: https://docs.pytest.org/en/stable/example/parametrize.html#apply-indirect-on-particular-arguments

    【讨论】:

    • 我有一个类似结构的测试用例。当我执行您的建议时,我收到一条错误消息,提示 fixture 'return code' not found。显然,设置indirect=True 要求@pytest.mark.parametrize() 中的所有参数都是函数夹具。你知道如何让 @pytest.mark.parametrize() 适应函数装置和参数吗?
    • @merowinger 是的,您可以通过将间接参数名称列表传递给 indirect 来将间接应用于特定参数。我会更新我的答案。
    • 嗨@damon,在遵循您建议的结构和解决方案时,我找不到夹具凭据。知道为什么即使我在间接列表中列出了凭据,它为什么还要尝试查找凭据?
    • @BICube indirect 是要调用的其他灯具的列表。如果您想直接使用该值,而不是调用具有该名称的夹具,请不要将其包含在 indirect 列表中。
    • 这个例子似乎不完整。您仍然需要一个 credentials 夹具,它将字符串 "userX" 转换为 userX 对象。按原样运行此示例会导致出现fixture 'credentials' not found 错误消息。
    【解决方案2】:

    据我所知,你不能。有一些解决方法:

    1:放弃参数化

    def test_login():
        for user, return_code in [
          (User("user1", ...), 200),
          (User("user2", ...), 200),
          (User("user3", ...), 401),
        ]:
            # test user
            pass
    

    def test_login(user1, user2, user3):
        for user, return_code in [
          (user1, 200),
          (user2, 200),
          (user3, 401),
        ]:
            # test user
            pass
    

    专业版:简单
    缺点:如果第一个测试失败,后面的测试就不会运行。错误消息也没有显示哪个测试用例失败了。

    2:使用索引或名称进行参数化

    @pytest.mark.parametrize('idx', [0,1,2])
    def test_login(idx):
        user, return_code = [
          (User("user1", ...), 200),
          (User("user2", ...), 200),
          (User("user3", ...), 401),
        ][idx]
            # test user
            pass
    

    (或使用user1user2user3 固定装置的等价物)

    专业版:pytest 识别单独的测试
    缺点:无用的索引/名称

    3:使用间接参数化夹具

    @pytest.mark.fixture
    def user_and_code(request):
        if request.param == "user1":
            return User("user1", ...), 200
        elif request.param == "user2":
            return User("user2", ...), 200
        elif request.param == "user3":
            return User("user3", ...), 401
    
    @pytest.mark.parametrize('user_and_code', ['user1', 'user2', 'user3'], indirect=True)
    def test_login(user_and_code):
        user, return_code = user_and_code
    
        # test user
        pass
    

    或者

    @pytest.mark.fixture
    def user_and_code(request, user1, user2, user3):
        return [
            (user1, 200),
            (user2, 200),
            (user3, 401),
        ][request.param]
    
    @pytest.mark.parametrize('user_and_code', [0,1,2], indirect=True)
    def test_login(user_and_code):
        user, return_code = user_and_code
    
        # test user
        pass
    

    Pro:测试代码很干净,fixture 可以在其他测试中重用
    缺点:更复杂

    根据文档,间接参数化是为了在测试收集期间跳过昂贵的设置,而不是让固定装置可用于参数,正如您所见,这不是很好。

    在上述每个选项中,有几个选项可以混合搭配,例如实例化所有用户或使用 if/else 仅实例化您需要的内容,使用 user1 等固定装置或内联创建用户,使用名称或索引等。示例仅显示了一些替代方案。

    【讨论】:

      猜你喜欢
      • 2021-01-02
      • 2020-12-05
      • 2021-12-08
      • 1970-01-01
      • 2021-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-20
      相关资源
      最近更新 更多