When writing tests, ideally you should verify your code’s behavior not only in the usual, “happy” cases, but also in the erroneous ones. Although you may very well accept that a function blows when feed with incorrect data, it should blow up predictably and consistently. An error, exception or panic is still an output; and it should be possible to capture and examine it in tests.
unittest module has a couple of ways to deal with expected error. Probably the most useful among them is the
TestCase.assertRaises method. It does pretty much exactly what it names hints to: asserting that a piece of code raises a specific type of exception:
In Python 2.7 (or with the unittest2 shim library), it can be also used much more conveniently as a context manager:
assertRaises will execute given block of code (or a callable, like in the first example) and throw
AssertionError if an exception of given type was not raised by the code. By calling the tested function with incorrect data, we intend to provoke the exception, affirm the assertion, and ultimately have our test pass.
I mentioned, however, that it doesn’t just matter if your code blows up in response to invalid input or state, but also how it does so. Even though
assertRaises will verify that the exception is of correct type, it is often not nearly enough for a robust test. In Python, exception types tend to be awfully broad, insofar that a simple information about throwing
TypeError may tell you next to nothing about the error’s true nature.
Ironically, designers of the
unittest module seemed to be vaguely aware of the problem. One of their solutions, though, was to introduce a proverbial second problem, taking the form of
assertRaisesRegexp method. While it may kinda-sorta work in simple cases, I wouldn’t be very confident relying on regular expressions for anything more complex.
Especially when the other possible approach appears much more sound anyway. Using the feature of
with statement, we can capture the exception object and examine it ourselves, in a normal Python code. Not just the type or message (though these are typically the only reliable things), but also whatever other data it may carry:
Sometimes, those checks might grow quite sophisticated. For example, in Python 3 you have exception chaining; it allows you to look not only at the immediate exception object, but also its
__cause__, which is analogous to
Throwable.getCause in Java or
Exception.InnerException in C#. If you need to dig this deep, I’d suggest extracting a function with all that code – essentially a specialized version of
assertRaises, preferably with all the context manager goodness that would enable us to use it like the original.
As it turns out, this can be very simple.
Technically, a context manager is an object with an
__exit__ methods. This would immediately suggest that we need to implement them as classes, which sounds like quite a lot of boilerplate to encapsulate what typically amounts to just a few lines of code.
But fortunately, a class is very much optional. A much more concise, and often easier option, is to use the
contextmanager decorator, found in the standard
contextlib module. What it does is making it possible to implement
with-enabled objects as simple, short functions:
Okay, I misspoke: as simple, short generator functions. As you can see above, the decorator uses the
yield statement in rather clever way. Conceptually, it “marks” the place where the code inside the
with block would be normally inserted. Additionally, the object we yield (
r, in this case) is the same one that’s captured by the
as clause, if any, of a
with statement that uses our context manager.
Here’s how such usage looks like:
We can actually afford to ignore the exception object now. All the necessary checks and assertion are now performed by our customized,
assertRaises-like method. Of course, we can also use it in as many different tests as we want.
Adding comments is disabled.Comments are disabled.