Dealing Too Many Cards
A deck contains 52 cards. In real life, when I’ve dealt all the cards from a deck, there are physically no more cards left to deal. In Java, it’s possible for a client to continue calling the deal method after 52 cards have been dealt. What should happen?
Of course, the real solution to this problem is to write client code that doesn’t deal more cards than it should. We could simply stop there by providing a Javadoc comment to that effect. "Don’t do that," we’d say in the Javadoc, "and if you do that, you’re on your own. In fact, we don’t even know what’s going to happen if you do that."
Remember that a primary goal of tests in TDD is to document things. So even if client code shouldn’t "do that," the client programmer has to know about that constraint. Sure, they could read the Javadoc, but why not supply them with tests that demonstrate how you can get into trouble by dealing too many cards? These tests are trustworthy, unlike Javadoc, which is only as good as the last programmer who bothered to update it.
We can begin to write a test in the class DeckTest that documents dealing too many cards (see Listing 1).
Listing 1 Starting the test in DeckTest.
public void testDealTooMany() { assertDealAll(deck1, new ArrayList<Card>()); deck1.deal(); }
For purposes of this test, we deal all the cards into a new ArrayList; we can ignore these cards.
At the end of part 4 of this series, I implored you to refactor your test code in DeckTest to eliminate duplication with other tests. As a result, you might have built an assertDealAll method. In case you didn’t, though, it’s shown in Listing 2.
Listing 2 assertDealAll method.
private void assertDealAll(Deck deck, List<Card> hand) { for (int i = Deck.SIZE; i > 0; i--) { Card card = deck.deal(); assertDeal(deck, card, i); hand.add(card); } assertEquals(0, deck.cardsRemaining()); } private void assertDeal(Deck deck, Card card, int i) { assertEquals(i - 1, deck.cardsRemaining()); assertFalse(deck.contains(card.getRank(), card.getSuit())); }
Right now we’re not sure what will happen if we run testDealTooMany, so let’s run it and observe. It turns out that we get a java.util.NoSuchElementException. That seems like a reasonably expressive exception type, one that we could consider propagating to client code. Note also that JUnit reports an error instead of a failure, meaning that the test method threw an exception that was not caught.
The simplest way of coding the test to show that we expect this exception is as shown in Listing 3.
Listing 3 An exception expectation.
public void testDealTooMany() { assertDealAll(deck1, new ArrayList<Card>()); try { deck1.deal(); fail("should have received exception"); } catch (NoSuchElementException expected) { // success! } }
First, we wrap the code that could generate the exception in a try-catch block. Following the statement(s) that could generate exceptions, we code a fail statement. The fail method is defined in junit.framework.TestCase. If test code invokes the fail method, the test doesn’t pass, and JUnit executes no further statements in the test method.
If the code does generate an exception, as we expect, control transfers to the catch block. That’s a good thing, and we can document it in the test by naming the caught object expected, or perhaps success.
Other ways exist of coding tests to expect exceptions. However, the idiom above is succinct and simple enough to code. It also appears to be the most commonly used technique (see this discussion for some of the other variants).