You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pytest function作用域fixture属性未在单测间重置问题咨询

问题根因

出现这个现象的核心原因是两个测试用例拿到的是同一个Foo实例,fixture的销毁重建逻辑根本没有生效,和类的默认参数写法没有直接关系。
首先明确pytest function级别fixture的正常行为:scope='function'是fixture的默认作用域,正常逻辑是每个测试用例执行前都会重新运行一次fixture函数,生成全新的依赖对象,用例跑完就销毁。你现在能看到前一个用例修改的r属性残留,说明两个用例根本没拿到新实例。
最常见的触发场景有三个:

  • fixture实际返回的是外部预定义的全局实例,而不是每次调用时新建的对象。比如你写代码的时候为了省事提前在fixture外面实例化了Foo,fixture里直接返回这个外部对象:
# 错误写法,所有调用都返回同一个实例
_global_foo = Foo()
@pytest.fixture(scope="function")
def foo():
    return _global_foo
  • 存在同名高作用域fixture覆盖了当前的function级fixture。比如你在项目根目录的conftest.py里定义了一个session/module/class级别的同名foo fixture,测试文件里的fixture因为优先级或者写法问题没生效,所有用例就会共用那个高作用域的单例。
  • 作用域参数拼写错误或者被意外修改。比如你实际写的是scope="class"或者scope="module",自己没注意到,导致同一测试类/模块下的用例共用同一个实例。

快速排查方法

在两个测试用例里打印实例的内存ID就行,如果两个ID完全一致,就实锤是拿到了同一个实例,顺着上面三个场景查fixture定义就能找到问题:

def test_bar(foo):
    print(id(foo))
    foo.r = range(20)

def test_hop(foo):
    print(id(foo))
    # 两个ID相同就说明fixture没重建

额外注意:Python可变默认参数陷阱

虽然你这次的问题不是这个导致的,但你当前的类写法埋了个很容易踩的坑:Python函数的默认参数是在函数定义的时候就求值生成的,后续所有不传对应参数的调用,都会共享这同一个默认参数对象。
你现在写的r=range(10)在Python3里是不可变的range对象,不会触发问题,但如果你后续把默认值改成列表、字典、自定义类实例这类可变对象,又在代码里原地修改这个对象而不是给属性重新赋值,就会出现所有实例共享属性的诡异问题:

# 反面教材:可变默认参数
class Foo:
    def __init__(self, r=list(range(10))):
        self.r = r

f1 = Foo()
f1.r.append(99) # 原地修改默认参数对应的列表
f2 = Foo()
print(f2.r) # 输出[0,1,2,...,9,99],新实例也被影响

这类场景的标准写法是用None作为默认参数占位,在函数内部初始化实际的默认值:

class Foo:
    def __init__(self, r=None):
        if r is None:
            r = range(10)
        self.r = r

这样每次实例化的时候都会生成新的range对象,不会出现共享问题。

内容的提问来源于stack exchange,提问作者Sourya Dey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 14:06:29