Pytest/Mock在捕获异常时保留多余的对象引用

2 投票
1 回答
26 浏览
提问于 2025-04-14 18:06

我在使用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>]

撰写回答