[TIP] Asserting behaviour of exception tracebacks
Ben Finney
ben at benfinney.id.au
Wed Jan 30 20:37:55 PST 2008
Howdy all,
I have some code that handles exceptions from a lower level, applies
contextual knowledge where the exception is caught, and raises a
more-informative error. The error is wrapped in a module specific
error class::
class ModuleSpecificError(RuntimeError):
def __init__(self, orig, context):
self.orig = orig
self.context = context
def __str__(self):
orig_name = orig.__class__.__name__
msg = (
"failed during %(context)s"
" (%(orig_name)s: %(orig)s)"
) % vars(self)
That's okay as far as it goes. (It even includes the original error
classname and message in the ModuleSpecificError message.)
An example of code that's currently using this might be::
def problem():
empty = {}
# This will raise a KeyError
foo = empty['bar']
def handle_problem():
# Some arbitrary context for the error
context = objecT()
try:
problem()
except Exception, exc:
error = ModuleSpecificError(orig=exc, context=context)
raise error
But this makes the classic mistake of losing the traceback of the
original error: the ModuleSpecificError gets raised with a traceback
starting at the 'raise error' statement, which is useless for
debugging.
The solution for this is to change the handling to preserve the
traceback::
def handle_problem():
# Some arbitrary context for the error
context = objecT()
try:
problem()
except Exception, exc:
exc_class, exc_instance, exc_traceback = sys.exc_info()
error = ModuleSpecificError(orig=exc, context=context)
raise error, None, exc_traceback
That will raise the specific error instance I want, complete with its
informative context-specific message; but the traceback will lead all
the way to the original problem (the statement that causes a
KeyError).
Great, I now have an implementation change I can make to alter the
application behaviour. The issue is: how can I test this change? I'm
practicing TDD/BDD, and this is a change in externally-visible
behaviour. I therefore need a unit test to assert that the behaviour
has actually changed as specified.
The 'unittest' module in Python gives me 'TestCase.failUnlessRaises'
to assert that an error of a specific class *is* raised from a
particular function call. I can't see a clean way to assert that
*when* that exception is raised, the traceback contains the original
error occurence and doesn't just stop at the ModuleSpecificError
occurrence.
I don't even know how I'd really test that in code; even if I did know
how, I'd immediately want to be able to parameterise it so that I
don't have to keep writing it over again. How can I go about doing
that?
--
\ "It's a small world, but I wouldn't want to have to paint it." |
`\ -- Steven Wright |
_o__) |
Ben Finney
More information about the testing-in-python
mailing list