Pytest/Mock在捕获异常时保留多余的对象引用
我在使用pytest和mock的时候遇到了一个奇怪的问题:我想通过使用del ...
来删除一个对象,从而触发它的__del__
方法。根据文档,del
只是减少了被“删除”对象的引用计数,只有当没有其他地方还在引用这个对象时,才会真正删除它。看起来如果Mock抛出了一个异常,就会导致某个地方意外地保留了这个对象的额外引用。
我做了一个简单的测试来展示这个问题:test_del_passes
(这个测试顺利完成)和test_del_fails
(这个测试在最后一个断言失败,即del del_test
没有触发del_test.__del__()
)之间唯一的区别是,前者中test_fn
返回了一个值,而后者中test_fn
抛出了一个TimeoutError
。我尝试删除test_fn
对象,或者把TimeoutError
赋值给一个变量再删除那个变量,但我就是找不到让第二个测试通过的方法。所以在测试的基础设施中,有人保留了del_test
的额外引用,我不知道是谁、为什么会这样,也不知道该如何解决。
import unittest.mock as mock
class DelTest:
def __init__(self, flags, test_fn):
self.flags = flags
self.test_fn = test_fn
def __del__(self):
self.flags[0] = 1
def run(self):
try:
self.test_fn()
except TimeoutError:
pass
def test_del_passes():
flags = [0]
test_fn = mock.Mock(side_effect=[True])
del_test = DelTest(flags, test_fn)
del_test.run()
assert flags[0] == 0
del del_test
assert flags[0] == 1
def test_del_fails():
flags = [0]
test_fn = mock.Mock(side_effect=[TimeoutError()])
del_test = DelTest(flags, test_fn)
del_test.run()
assert flags[0] == 0
del del_test
assert flags[0] == 1
1 个回答
3
这个框架会保持对对象的引用,你可以通过调用 gc.get_referrers(del_test)
来查看这个引用:
[<frame at 0x101b15080, file 'xxx.py', line 17, code run>]