当调用pytest中的班级参数化固定装置时,类固定固定装置的意外行为

发布于 2025-02-11 05:54:12 字数 1640 浏览 1 评论 0原文

我在类中有一个带有参数化的间接<代码>类的测试 scoped固定装置。当另一个常规范围固定在调用参数化固定装置时,其执行似乎更函数 scop。表示每种测试方法再次调用常规固定装置。在删除参数化的灯具调用时,它将按预期执行一次。

我知道固定的生命周期,如在这里,但是我没有离开班级。我在这里缺少什么?

PYTEST-7.0.1,PYTEST-ASYNCIO-0.17.2

代码:

@pytest_asyncio.fixture(scope='class')
async def some_fixture(request, iteration):  # <---- when not using 'iteration', works as expected
    print(f'-some_fixture-{request.scope}-start')
    yield
    print(f'-some_fixture-{request.scope}-end')

@pytest_asyncio.fixture(scope='class')
async def iteration(self, request):
    print(f'-iteration-{request.scope}-start')
    yield request.param
    print(f'-iteration-{request.scope}-end')

@pytest.mark.parametrize('iteration', range(1, ITERATIONS + 1), indirect=True)  # <---- tried adding scope='class', no success
@pytest.mark.asyncio
class Something:
    async def test_something(self, iteration, some_fixture):
        print(test_body)

输出:

test_foo.py::test_something[1] -iteration-class-start
-some_fixture-class-start
-test_body
PASSED
test_foo.py::test_something[2] -some_fixture-class-end  # <---- why it end here?
-iteration-class-end
-iteration-class-start
-some_fixture-class-start  # <--- called twice
-test_body
PASSED                                                                                                                                                     
-some_fixture-class-end
-iteration-class-end

I have a test in a class with parametrized indirect class scoped fixture. When another regular class scoped fixture is calling the parametrized fixture, its execution seems to be more function scoped. Meaning the regular fixture is called again, for every test method. When removing the parametrized fixture call, it is executed once per class as expected.

I am aware of fixture life cycle as explained here, but I am not exiting the class. What I am missing here?

pytest-7.0.1, pytest-asyncio-0.17.2

Code:

@pytest_asyncio.fixture(scope='class')
async def some_fixture(request, iteration):  # <---- when not using 'iteration', works as expected
    print(f'-some_fixture-{request.scope}-start')
    yield
    print(f'-some_fixture-{request.scope}-end')

@pytest_asyncio.fixture(scope='class')
async def iteration(self, request):
    print(f'-iteration-{request.scope}-start')
    yield request.param
    print(f'-iteration-{request.scope}-end')

@pytest.mark.parametrize('iteration', range(1, ITERATIONS + 1), indirect=True)  # <---- tried adding scope='class', no success
@pytest.mark.asyncio
class Something:
    async def test_something(self, iteration, some_fixture):
        print(test_body)

Output:

test_foo.py::test_something[1] -iteration-class-start
-some_fixture-class-start
-test_body
PASSED
test_foo.py::test_something[2] -some_fixture-class-end  # <---- why it end here?
-iteration-class-end
-iteration-class-start
-some_fixture-class-start  # <--- called twice
-test_body
PASSED                                                                                                                                                     
-some_fixture-class-end
-iteration-class-end

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

我不在是我 2025-02-18 05:54:12

这是参数化灯具的预期行为。可以将固定装置直接进行参数化(通过夹具装饰器中的params参数)或通过测试间接参数(如问题所示)。两种情况在语义上都是等效的,意味着将以不同的参数调用固定装置并产生不同的结果。因此,必须分别评估每个参数的固定装置 - 您可以将其视为每个参数而不是单个夹具的单独固定装置。

如果固定设备(示例中的some_fixture)是从参数化的固定装置“派生”的,则也将被参数化,因为每个参数都会为每个参数计算一个单独的结果,也将是相同的。为了说明这一点,您可以将参数添加到输出中的固定装置中。这是一个稍有变化的示例来证明这一点:

ITERATIONS = 2

@pytest.fixture(scope='class')
def some_fixture(request, iteration):
    print(f'-some_fixture-{request.scope}-{iteration}-start')
    yield
    print(f'-some_fixture-{request.scope}-{iteration}-end')

@pytest.fixture(scope='class')
def iteration(request):
    print(f'-iteration-{request.scope}-{request.param}-start')
    yield request.param
    print(f'-iteration-{request.scope}-{request.param}-end')

@pytest.mark.parametrize('iteration', range(1, ITERATIONS + 1),
                         indirect=True)
class TestSomething:
    def test_something(self, some_fixture):
        print("test_something")

    def test_something_else(self, some_fixture):
        print("test_something_else")

我删除了异步部分,因为它与问题无关,因此将参数添加到固定装置输出中,并在类中添加了第二个测试(还对测试名称进行了调整以识别。由pytest)。

输出的是:

-iteration-class-1-start
-some_fixture-class-1-start
PASSED test_something
PASSED test_something_else
-some_fixture-class-1-end
-iteration-class-1-end
-iteration-class-2-start
-some_fixture-class-2-start
PASSED test_something
PASSED test_something_else
-some_fixture-class-2-end
-iteration-class-2-end

请注意,即使多次使用(在两次测试中,在这种情况下两次),每个参数仅适用于每个参数一次。如您所见,它的确表现得像类固定装置,如果具有不同参数的固定装置被视为不同的固定装置(如应该)。

This is the expected behavior for parametrized fixtures. A fixture can be directly parametrized (via the params argument in the fixture decorator) or indirectly parametrized by a test (as shown in the question). Both cases are semantically equivalent, and mean that a fixture will be called with different parameters and yield different outcomes. Therefore the fixture has to be evaluated for each parameter separately - you can think of this as a separate fixture for each parameter instead of a single fixture.

The same is true if a fixture (as some_fixture in your example) is "derived" from a parametrized fixture - it will also be parametrized, as a separate outcome is calculated for each parameter. To illustrate this, you can add the parameter to the fixture in the output. Here is a slightly changed example to demonstrate this:

ITERATIONS = 2

@pytest.fixture(scope='class')
def some_fixture(request, iteration):
    print(f'-some_fixture-{request.scope}-{iteration}-start')
    yield
    print(f'-some_fixture-{request.scope}-{iteration}-end')

@pytest.fixture(scope='class')
def iteration(request):
    print(f'-iteration-{request.scope}-{request.param}-start')
    yield request.param
    print(f'-iteration-{request.scope}-{request.param}-end')

@pytest.mark.parametrize('iteration', range(1, ITERATIONS + 1),
                         indirect=True)
class TestSomething:
    def test_something(self, some_fixture):
        print("test_something")

    def test_something_else(self, some_fixture):
        print("test_something_else")

I have removed the async part, as it is not relevant for the question, added the parameter to the fixture output and added a second test in the class (also adapted the test names to be recognized by pytest).

The output for this is:

-iteration-class-1-start
-some_fixture-class-1-start
PASSED test_something
PASSED test_something_else
-some_fixture-class-1-end
-iteration-class-1-end
-iteration-class-2-start
-some_fixture-class-2-start
PASSED test_something
PASSED test_something_else
-some_fixture-class-2-end
-iteration-class-2-end

Note that the fixture is called only once for each parameter, even if it is used multiple times (in this case twice in the 2 tests). As you can see, it indeed behaves like a class fixture, if fixtures with different parameters are seen as different fixtures (as they should).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文