在我的测试设置中,我不希望实际上为我的对象捕获异常,主要是因为这意味着回溯将无法预测。 (如果文件更改长度,则回溯中的行号将更改。)
如何将假追踪附加到异常,以便我可以对其格式化方式进行断言?这甚至可能吗?我正在使用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
""")
答案 0 :(得分:3)
阅读the source of 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
感谢@User提供FakeException
,这是必要的,因为真正的例外类型 - 检查with_traceback()
的参数。
此版本有一些限制:
它不会打印每个堆栈帧的代码行
追溯会,因为format_exception
去寻找
代码来自的真实文件(在我们的例子中不存在)。
如果你想使这项工作,你需要插入假数据
linecache
的
缓存(因为traceback
使用linecache
来获取源代码
代码),按@User's answer
below。
您也无法提升 exc
并期待假冒追溯
生存。
更一般地说,如果你有遍历回溯的客户端代码
与traceback
不同的方式(例如inspect
的大部分内容
模块),这些假货可能不起作用。你需要添加任何东西
客户端代码期望的额外属性。
这些限制对于我的目的来说很好 - 我只是将它用作调用traceback
的代码的测试双重 - 但是如果你想要做更多涉及回溯操作,它可能looks like你可能必须降到C级。
答案 1 :(得分:2)
EDIT2:
这是linecache的代码..我会评论它。
def updatecache(filename, module_globals=None): # module_globals is a dict
# ...
if module_globals and '__loader__' in module_globals:
name = module_globals.get('__name__')
loader = module_globals['__loader__']
# module_globals = dict(__name__ = 'somename', __loader__ = loader)
get_source = getattr(loader, 'get_source', None)
# loader must have a 'get_source' function that returns the source
if name and get_source:
try:
data = get_source(name)
except (ImportError, IOError):
pass
else:
if data is None:
# No luck, the PEP302 loader cannot find the source
# for this module.
return []
cache[filename] = (
len(data), None,
[line+'\n' for line in data.splitlines()], fullname
)
return cache[filename][2]
这意味着在你测试之前:
class Loader:
def get_source(self):
return 'source of the module'
import linecache
linecache.updatecache(filename, dict(__name__ = 'modulename without <> around',
__loader__ = Loader()))
和'source of the module'
是您测试的模块的来源。
EDIT1:
到目前为止我的解决方案:
class MyExeption(Exception):
_traceback = None
@property
def __traceback__(self):
return self._traceback
@__traceback__.setter
def __traceback__(self, value):
self._traceback = value
def with_traceback(self, tb_or_none):
self.__traceback__ = tb_or_none
return self
现在您可以设置例外的自定义回溯:
e = MyExeption().with_traceback(1)
如果你再加上例外,你通常会做什么:
raise e.with_traceback(fake_tb)
所有异常打印都会遍历此功能:
import traceback
traceback.print_exception(_type, _error, _traceback)
希望它有所帮助。
答案 2 :(得分:-1)
您应该能够在测试运行中简单地raise
您想要的任何假异常。 python异常文档建议你创建一个类并将其作为例外。这是文档的第8.5节。
http://docs.python.org/2/tutorial/errors.html
一旦你创建了这个类,应该非常简单。