Safe, Simple, Deadly
Making Exception Testing Easier
I do not like "returns false on failure." Even worse is "returns a result or some magic error value." Part of this is based in reason and part in irrational reaction to a personal history of dealing with really, really bad APIs. The result is that when I write a subroutine that returns a value, it's very likely to follow these simple rules:
return a value of a known type, if everything worked; "false" is okay too
if something went wrong, throw an exception
I do like testing my code. Testing case 1, from above, is easy!
That's nice and simple and straightforward! There's no code there that doesn't contribute to the very thing we're testing. What about testing case 2, though
That "eval and check result and capture
$@" is going to get pretty tedious, pretty quickly. Also, if our tests are part of some larger test program -- which they probably are -- then we're missing a bunch of extra safety. That's why we'd never use
eval, right? We all use Try::Tiny instead! With that, our code would look like this:
Woah, what? Now we're in a world of hurt, and there's no chance that we'll ever write that over and over. And what's up with that
finally block with the weird
die? Well, Perl has some really bizarre (read: awful) semantics with exception handling, and in a number of cases it can die but leave
$@ empty. Try::Tiny helps us deal with these situations, but only at the first order. Here, we want a higher-order check of our exception status.
Obviously, this is all lead in to the simple way to check this:
exception always returns a scalar: the exception that was thrown, if any, or
undef otherwise. In the event that the code died, but no exception could be found -- a highly problematic case --
exception itself will die. The routine's "returns a scalar" behavior makes it excellent for use inline in test assertions:
A Quick Note about Test::Exception
Test::Fatal is not the only library for this kind of testing. There also exists Test::Exception, which also provides tools for testing. Test::Exception has more moving pieces than Test::Fatal, including the highly complex Sub::Uplevel. In almost all cases, its complexity is not needed, and you will be better served by the simple
exception routine than by the handful of test assertions provided by Test::Exception -- but it takes all kinds.