Ben*_*son 3 python testing unit-testing mocking traceback
我正在编写一个测试运行程序。我有一个可以捕获和存储异常的对象,稍后将其格式化为字符串作为测试失败报告的一部分。我正在尝试对格式化异常的过程进行单元测试。
在我的测试设置中,我不想实际抛出异常让我的对象捕获,主要是因为这意味着回溯将不可预测。(如果文件改变长度,回溯中的行号也会改变。)
如何将虚假回溯附加到异常,以便我可以断言它的格式化方式?这甚至可能吗?我正在使用 Python 3.3。
简化示例:
class ExceptionCatcher(object):
def __init__(self, function_to_try):
self.f = function_to_try
self.exception = None
def try_run(self):
try:
self.f()
except Exception as e:
self.exception = e
def format_exception_catcher(catcher):
pass
# No implementation yet - I'm doing TDD.
# This'll probably use the 'traceback' module to stringify catcher.exception
class TestFormattingExceptions(unittest.TestCase):
def test_formatting(self):
catcher = ExceptionCatcher(None)
catcher.exception = ValueError("Oh no")
# do something to catcher.exception so that it has a traceback?
output_str = format_exception_catcher(catcher)
self.assertEquals(output_str,
"""Traceback (most recent call last):
File "nonexistent_file.py", line 100, in nonexistent_function
raise ValueError("Oh no")
ValueError: Oh no
""")
Run Code Online (Sandbox Code Playgroud)
阅读来源为traceback.py我指明了正确的方向。这是我的hacky解决方案,它涉及伪造回溯通常持有引用的框架和代码对象。
import traceback
class FakeCode(object):
def __init__(self, co_filename, co_name):
self.co_filename = co_filename
self.co_name = co_name
class FakeFrame(object):
def __init__(self, f_code, f_globals):
self.f_code = f_code
self.f_globals = f_globals
class FakeTraceback(object):
def __init__(self, frames, line_nums):
if len(frames) != len(line_nums):
raise ValueError("Ya messed up!")
self._frames = frames
self._line_nums = line_nums
self.tb_frame = frames[0]
self.tb_lineno = line_nums[0]
@property
def tb_next(self):
if len(self._frames) > 1:
return FakeTraceback(self._frames[1:], self._line_nums[1:])
class FakeException(Exception):
def __init__(self, *args, **kwargs):
self._tb = None
super().__init__(*args, **kwargs)
@property
def __traceback__(self):
return self._tb
@__traceback__.setter
def __traceback__(self, value):
self._tb = value
def with_traceback(self, value):
self._tb = value
return self
code1 = FakeCode("made_up_filename.py", "non_existent_function")
code2 = FakeCode("another_non_existent_file.py", "another_non_existent_method")
frame1 = FakeFrame(code1, {})
frame2 = FakeFrame(code2, {})
tb = FakeTraceback([frame1, frame2], [1,3])
exc = FakeException("yo").with_traceback(tb)
print(''.join(traceback.format_exception(FakeException, exc, tb)))
# Traceback (most recent call last):
# File "made_up_filename.py", line 1, in non_existent_function
# File "another_non_existent_file.py", line 3, in another_non_existent_method
# FakeException: yo
Run Code Online (Sandbox Code Playgroud)
感谢@User 提供FakeException,这是必要的,因为真正的异常类型检查了 的参数with_traceback()。
此版本确实有一些限制:
它不会像真正的回溯那样打印每个堆栈帧的代码行,因为它format_exception会寻找代码来自的真实文件(在我们的例子中不存在)。如果你想让这个工作,你需要将假数据插入到
linecache的缓存中(因为traceback用于linecache获取源代码),根据@User 下面的回答。
你也不能真正提出 exc并期望假回溯能够存活下来。
更一般地,如果您的客户端代码以与traceback(例如inspect
模块的大部分)不同的方式遍历回溯,则这些伪造可能不起作用。您需要添加客户端代码期望的任何额外属性。
这些限制对我来说很好——我只是将它用作调用代码的测试替身traceback——但如果你想做更多涉及的回溯操作,看起来你可能不得不深入到 C 级别。
| 归档时间: |
|
| 查看次数: |
2091 次 |
| 最近记录: |