为什么 pytest fixture 和直接调用的函数产生的值不同?

Why are the values yielded by a pytest fixture and a function called directly different?

在下面的代码中,我得到的是一个生成器对象

<generator object a at 0x7feb40b2d7b0>
from playwright.sync_api import sync_playwright

def get_playwright():
    with sync_playwright() as playwright:
        yield playwright

print(get_playwright())

但是当我使用pytest时,我得到的是一个class对象

<class 'playwright.sync_api._generated.Playwright'>`:
# conftest.py

import pytest
from playwright.sync_api import sync_playwright

@pytest.fixture()
def get_playwright():
    with sync_playwright() as playwright:
        yield playwright
# test_one.py

def test(get_playwright):
    print(get_playwright)

我想知道为什么会这样?如何在不使用 pytest 的情况下获得 class?

对于第一种形式:

def get_playwright():
    with sync_playwright() as playwright:
        yield playwright

print(get_playwright())  # <generator object get_playwright at 0x108aac580>

这是预期的,因为 get_playwrightgenerator, which returns a generator iterator, which you have to call next(...) 从迭代器获取每个产生的值。

考虑一个更简单的 non-playwright 示例:

In [14]: def generate_nums():
    ...:     for num in range(10):
    ...:         yield num
    ...: 

In [15]: nums = generate_nums()

In [16]: nums
Out[16]: <generator object generate_nums at 0x11115e6d0>

In [17]: next(nums)
Out[17]: 0

In [18]: next(nums)
Out[18]: 1

In [19]: next(nums)
Out[19]: 2

有关更多示例,请参阅 Understanding generators in Python

由于你的 get_playwright returns 是一个迭代器,你需要调用 next() 一次 来获取实际对象:

from playwright.sync_api import sync_playwright


def get_playwright():
    with sync_playwright() as playwright:
        yield playwright

playwright_generator = get_playwright()
print(playwright_generator)  # <generator object get_playwright at 0x104031580>

playwright = next(playwright_generator)
print(playwright)  # <playwright._impl._playwright.Playwright object at 0x1041aabb0>

对于第二种形式:

@pytest.fixture()
def get_playwright():
    with sync_playwright() as playwright:
        yield playwright

def test(get_playwright):
    print(get_playwright)

应该是一样的情况,但是只是如果pytest是一个生成器,它会自动调用next() fixture值。我无法从 pytest 文档中找到有关此行为的文档,但其中一位 pytest 作者提到了's/maintainer's :

Here's roughly the execution here

  • pytest notices your fixture is used for the test function
  • pytest calls the fixture function
    • since it is a generator, it returns immediately without executing code
  • pytest notices it is a generator, calls next(...) on it
    • this causes the code to execute until the yield and then "pausing". you can think of it kind of as a co-routine ...
  • pytest then executes your test function

...这可能就是为什么传递给测试函数的值已经是 next-ed 值,剧作家对象。