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级别的同名foofixture,测试文件里的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
相关产品推荐
相关产品推荐

