[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