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!
1: | use Gift::Wrapper qw(wrap); |
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
1: | use Gift::Wrapper qw(wrap); |
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:
1: | use Gift::Wrapper qw(wrap); |
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:
1: | use Gift::Wrapper qw(wrap); |
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:
1: | # test that we died |
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.