pytest:使用 pytest.mark.parametrize 和间接参数化将关键字 arg 传递给 fixture

pytest: passing keyword arg to fixture using pytest.mark.parametrize with indirect parameterization

我有一个 pytest.fixture,它有一个位置参数和一个关键字参数。

根据 Pass a parameter to a fixture function,可以使用 pytest.mark.parametrize 将 args 传递给 fixture,并将 indirect arg 设置为 fixture 的名称。

请看下面的示例代码。

import pytest

class Foo:
    def __init__(self, a: str, b: str):
        self.a = a
        self.b = b

@pytest.fixture
def a() -> str:
    return "alphabet"

@pytest.fixture
def foo_obj(a: str, b: str = "bar") -> Foo:
    return Foo(a, b)

@pytest.mark.parametrize("foo_obj", [("applesauce", "baz")], indirect=["foo_obj"])
def test_thing(foo_obj) -> None:
    assert foo_obj.a == "applesauce"
    assert foo_obj.b == "baz"

此测试当前失败:"applesauce""baz" 未传递到夹具 foo_obj

我的问题:

  1. 我在将 args 传递给夹具时做错了什么 foo_obj
  2. 是否可以在 pytest.mark.parametrize 装饰器调用中只输入 kwarg b
    • 注意:我对按照此处的建议包装夹具不感兴趣:Can I pass arguments to pytest fixtures?

版本

Python==3.8.5
pytest==6.0.1

我认为您在这里混合了两件事:通过 request.params 将参数传递给夹具,以及将夹具基于另一个(或多个)夹具。

要在夹具中使用 mark.parametrize 中使用的参数,您必须从 request.params 中获取它们,如您的链接问题所示:

@pytest.fixture
def foo_obj(request) -> Foo:
    return Foo(request.param[0], request.param[1])

你正在做的,是将 foo_obj 基于固定装置 a,意思是,a 是那个固定装置的结果(例如它总是 "a"), 和一个常量参数 b。两者都与 parametrize 设置的值无关 - 这些设置在 request.params 中,如上所示。

讨论隐式间接参数化的 put me on the scent of this answer

@pytest.fixture
def foo_obj(a: str, b: str) -> Foo:  # Note: b was changed to positional arg
    print("hi")
    return Foo(a, b)

@pytest.mark.parametrize("a, b", [("applesauce", "baz")])
def test_thing(foo_obj) -> None:
    assert foo_obj.a == "applesauce"
    assert foo_obj.b == "baz"

上述情况测试通过。

似乎这种隐式间接参数化可以用于位置参数,但不能用于关键字参数。例如,如果 b 作为关键字参数保留在夹具 foo_obj 中,pytest 将失败并显示消息:In test_thing: function uses no argument 'b'.

我想知道,是否可以通过这种方式对关键字参数进行参数化?


第二次尝试使用关键字 arg

我决定再次尝试拉动 and also this answer:

@pytest.fixture
def a() -> str:
    return "alphabet"

@pytest.fixture
def foo_obj(request, a) -> Foo:
    return Foo(request.param.get("a", a), request.param.get("b", "bar"))

@pytest.mark.parametrize("foo_obj", [dict(a="applesauce", b="baz")], indirect=True)
def test_thing(foo_obj) -> None:
    # Override both defaults, passes
    assert foo_obj.a == "applesauce"
    assert foo_obj.b == "baz"

@pytest.mark.parametrize("foo_obj", [dict(b="baz")], indirect=True)
def test_thing_only_b(foo_obj) -> None:
    # Use the default for a
    assert foo_obj.a == "alphabet"
    assert foo_obj.b == "baz"

这有效!我觉得它与接受两个参数的 foo_obj 夹具有点令人费解,有时它会使用一个或另一个。