← 返回题目列表

pytest fixture 的作用域和 yield 清理机制怎么理解?

高频 中等 第 5 / 27 题 更新于 2026/07/27
pytestfixture资源管理

简化版

fixture 用来给测试提供可复用的前置资源,比如测试数据、数据库连接、临时目录、客户端对象。它支持 functionclassmodulepackagesession 等作用域,也支持用 yield 在测试结束后执行清理逻辑。

详细版

fixture 的核心能力有三点:

  • 通过参数名自动注入资源;
  • 通过 scope 控制创建频率;
  • 通过 yield 管理资源释放。

示例:

@pytest.fixture(scope="function")
def db_session():
    session = create_session()
    yield session
    session.rollback()
    session.close()

常见作用域:

  • function:每个测试函数执行一次,隔离性最好;
  • class:每个测试类执行一次;
  • module:每个测试文件执行一次;
  • session:整个测试会话执行一次,适合昂贵但可共享的资源。

面试要强调:作用域越大,性能可能越好,但隔离性越弱。

完整版教学

一、fixture 要解决的重复初始化问题

真实测试很少只调用一个纯函数。更多时候,测试需要准备用户、订单、数据库、缓存、测试客户端、临时文件。如果每个测试都手写这些准备逻辑,代码会又长又容易不一致。

fixture 就是 pytest 提供的测试资源管理机制。它把“准备资源”和“测试逻辑”拆开,让测试函数只声明自己需要什么:

@pytest.fixture
def user():
    return {"id": 1, "name": "Tom"}

def test_user_id(user):
    assert user["id"] == 1

这里的 user 参数不是普通参数,而是 pytest 自动注入的 fixture 返回值。

二、fixture 的依赖关系

fixture 之间也可以相互依赖:

@pytest.fixture
def db_session():
    return create_session()

@pytest.fixture
def user(db_session):
    return create_user(db_session, name="Tom")

测试函数只需要声明 user,pytest 会先创建 db_session,再创建 user。这让测试资源可以分层组织。

不过要小心:依赖层次太深会降低可读性。读测试时,如果要跳五六个 fixture 才知道数据怎么来的,维护体验会很糟。

三、fixture 作用域怎么选

fixture 默认作用域是 function,也就是每个测试函数都会重新执行一次。这种隔离性最好,因为测试之间不会共享可变状态。

如果资源创建成本很高,可以扩大作用域。例如一个只读配置对象可以用 session,一个只读测试客户端可以用 module,但数据库事务、用户状态这类容易变化的资源更适合 function

常见作用域可以这样理解:

function:每个测试一次,最隔离
class:每个测试类一次
module:每个测试文件一次
package:每个测试包一次
session:整个 pytest 运行过程一次

作用域越大,资源复用越多,测试可能更快;但如果资源会被修改,测试之间就可能互相影响。

四、yield fixture 如何清理资源

fixture 如果只 return,只能创建资源,无法自然表达清理逻辑。yield fixture 可以把资源创建和释放放在一起:

@pytest.fixture
def temp_file():
    path = create_temp_file()
    yield path
    remove_file(path)

yield 前的代码在测试前执行,yield 后的代码在测试结束后执行。即使测试失败,清理逻辑也会执行。

数据库 session 常见写法:

@pytest.fixture
def db_session():
    session = SessionLocal()
    try:
        yield session
    finally:
        session.rollback()
        session.close()

这样每个测试结束后回滚并关闭 session,保证测试之间不互相污染。

五、autouse fixture 要谨慎

pytest 支持 autouse=True,表示测试不声明也会自动执行:

@pytest.fixture(autouse=True)
def clear_cache():
    cache.clear()

它适合一些全局清理逻辑,比如每个测试前清空缓存。但它也容易制造隐式行为:测试函数看起来没有依赖,实际却执行了很多逻辑。

所以 autouse 要少用,只用于非常明确、团队都知道的公共行为。其他资源更建议显式声明,让测试依赖一眼可见。

六、conftest.py 的作用

pytest 会自动加载 conftest.py 中定义的 fixture。通常可以把跨多个测试文件复用的 fixture 放在 tests/conftest.py

例如:

tests/
  conftest.py
  test_user.py
  test_order.py

conftest.py 不需要被 import,pytest 会自动发现。它适合放测试客户端、数据库 session、登录用户等公共 fixture。

但也不要把所有东西都塞进一个巨大的 conftest.py。大型项目可以按目录拆分,让不同模块拥有自己的局部 fixture。

七、常见误区与追问

作用域创建次数示例适合资源
function100 个测试创建 100 次可变对象、DB session、用户数据
module1 个文件创建 1 次只读配置、测试客户端
session整次运行创建 1 次昂贵且不可变的全局资源
yield fixture 生命周期:
创建资源 -> yield 给测试 -> 测试通过或失败 -> 执行 yield 后清理代码

易错点:scope 越大越省时间,但隔离性越弱;测试稳定性通常比省几秒初始化更重要。

第一个误区是用 session 作用域共享可变对象。比如所有测试共用同一个列表、同一个数据库 session,某个测试改了数据,后面的测试就可能莫名失败。

第二个误区是 fixture 名称不清楚。dataobjresource 这类名字太泛,读测试时很难判断它提供什么。

第三个误区是为了复用而过度抽象。测试代码允许有少量重复,过度抽象反而会让用例意图变模糊。

  • 误区:session 作用域一定更高效所以应该优先用。 作用域变大确实减少创建次数,但共享可变状态会让测试顺序依赖和偶发失败更难排查。
  • 误区:fixture 名越短越方便。 dataobjresource 这类名字信息量太低,读测试时无法判断它提供的业务语义。
  • 误区:autouse=True 可以减少所有显式声明。 autouse 会制造隐式执行逻辑,适合全局清理这类明确行为,不适合隐藏复杂业务准备。
  • 追问:yield fixture 测试失败时还会清理吗? 会执行 yield 后面的清理逻辑,因此适合关闭文件、回滚事务、释放临时目录。
  • 追问:fixture 之间能不能依赖? 可以,pytest 会按依赖图先解析被依赖 fixture,但层级过深会损害可读性。
  • 追问:conftest.py 应该放什么? 适合放跨多个测试文件复用的公共 fixture;大型项目应按目录拆分,避免一个巨大文件承载所有测试魔法。

八、加强记忆

记 fixture:它是测试资源的声明式注入机制;scope 决定资源复用范围,yield 决定资源如何清理。默认选 function 保隔离,只有资源昂贵且不可变时才扩大作用域;显式 fixture 比隐式魔法更容易维护。