使用冻结异常时单元测试出现意外FrozenInstanceError的解决方法
问题:unittest中抛出带冻结异常上下文的BugException时框架崩溃
问题背景
编写单元测试展示Bug失败场景时,要求测试报错且错误信息清晰指向BugException,但当BugException在捕获被@attrs.frozen装饰的异常类实例后抛出时,unittest框架会崩溃,真实的BugException被隐藏,反而输出FrozenInstanceError。
最小复现代码
import unittest import attrs @attrs.frozen class FrozenException(Exception): pass class BugException(Exception): pass class Test(unittest.TestCase): def testFromNone(self): raise BugException def testFromFrozen(self): try: raise FrozenException except FrozenException: raise BugException if __name__ == '__main__': unittest.main()
异常现象
- 执行
testFromNone时符合预期,测试失败并明确显示BugException:
$ python3 -m unittest scratch.Test.testFromNone E ====================================================================== ERROR: testFromNone (scratch.Test.testFromNone) ---------------------------------------------------------------------- Traceback (most recent call last): ... scratch.BugException ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (errors=1)
- 执行
testFromFrozen时,unittest框架直接崩溃,输出冗长堆栈跟踪,最终错误为attr.exceptions.FrozenInstanceError,真实的BugException被完全隐藏:
$ python3 -m unittest scratch.Test.testFromFrozen Traceback (most recent call last): ... <very long traceback> ... attr.exceptions.FrozenInstanceError
原因分析
unittest框架会对测试未捕获的异常做后处理,过程中尝试修改异常的堆栈追踪信息。由于BugException的__context__属性指向了冻结的FrozenException实例,框架修改时触发了冻结实例不可修改的限制,抛出FrozenInstanceError。
解决方案
方案1:抛出BugException时清除异常上下文
在抛出BugException时使用raise ... from None语法,显式清除异常上下文,避免unittest访问冻结的实例:
def testFromFrozen(self): try: raise FrozenException except FrozenException: # 清除上下文,阻止unittest访问冻结的FrozenException实例 raise BugException from None
方案2:允许修改冻结异常的__traceback__属性
通过attrs的field配置,单独允许修改异常类的__traceback__属性(unittest框架会尝试修改该属性),同时保留其他属性的冻结状态:
import attrs @attrs.frozen class FrozenException(Exception): # 允许修改__traceback__属性,适配unittest的后处理逻辑 __traceback__ = attrs.field(init=False, default=None, on_setattr=attrs.setters.allow)
验证结果
采用任意一种方案后,执行testFromFrozen会正常失败,错误信息正确指向BugException:
$ python3 -m unittest scratch.Test.testFromFrozen E ====================================================================== ERROR: testFromFrozen (scratch.Test.testFromFrozen) ---------------------------------------------------------------------- Traceback (most recent call last): ... scratch.BugException ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (errors=1)
内容的提问来源于stack exchange,提问作者ApproachingDarknessFish
相关产品推荐
相关产品推荐

