The Joy of Exceptions
One more recipe for the TDD cookbook before I move on to more
interesting things. You might have noticed a new GameOverException
and a try-catch block in the final example of my last post.
Before adding a game over exception, methods that checked for final
game states directly called System.exit()
. Testing this proved
difficult, and I ended up importing a third-party library of extra
JUnit rules. This week, I refactored them to throw a custom exception:
1 2 3 4 5 6 7 |
|
This exception will bubble up until it’s caught in Main
, which calls
System.exit()
on behalf of any method that throws a
GameOverException
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
This is not only a better practice, but also provides a much better way to test methods that might detect a completed game—just add a try/catch block to the tests!
Exceptions have come in handy elsewhere in my tests, too. Once the
controller starts a game, there’s nothing to break the back-and-forth
game loop but a win, draw, or error. Figuring out how to test game
states without getting stuck in an infinite loop or loading up entire
games was a challenge. “If only there
were some special syntax for breaking normal control flow in special
situations,” I wondered to myself more times than I’d like to admit.
Well, duh—use exceptions! My mock view objects throw a NoSuchElementException
when their input
queue runs empty. Catching this exception breaks the normal game flow
and allows me to access game state as soon as the fake input I’m
interested in has been sent to the controller. Here’s an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Normally, Controller.playRound()
will continue querying players for
moves until the game ends. But once this test catches the empty queue
exception, it tests against the expected output, which should show an
error message. Exceptions have proved extremely handy so far—as long as I remember
that they’re in my control flow toolbox, too.