Crafting Java with Test-Driven Development, Part 4: Shuffling and Dealing
- Test-Driving Randomness
- Overtesting
- Inhibiting Refactoring
- A Solution
Test-Driving Randomness
We’ve made some progress in developing our poker game. In part 3, we got sidetracked with building and writing a test for a hash code. As valuable as that exercise was, I’m getting anxious to deal a poker hand—aren’t you? But we can’t do that until we shuffle the cards. The cards are in a specific order in the deck, due to the way we’ve built it. We could ask for a random card from the deck each time we need to deal a card, but I think that violates the spirit of the requirement. Decks should be shuffled, and we should be dealing the topmost card from the deck each time.
The challenge: How do you both prove and document in a test that the deck is shuffled?
- One way would be to deal a card from an unshuffled Deck object, create another Deck object, and then ensure that the two cards weren’t the same. The problem is that the cards need to be the same, on average, 1 out of every 52 times. That approach would cause such a test to fail fairly often.
- Another way to prove that the deck gets shuffled is to demonstrate that
Deck code actually invokes some routine to shuffle the cards. This is a
technique that some people call interaction-based testing. (See Martin
Fowler’s article
"Mocks Aren’t Stubs.")
To use the interaction-based testing technique, we would create a mock implementation of the shuffling routine (whether a method or a class in its own right). The sole job of this mock would be to record the fact that it got called, potentially capturing arguments passed to it. A test would expect to be able to extract those facts. If the mock got called—and called with the correct arguments—the test to demonstrate shuffling would pass. Otherwise, the test would fail.
- Yet another technique would involve considering how the shuffling code might
look in the Deck object. Right off the bat, I’m thinking that we
would like to use the java.util.Collections class utility method
shuffle. There are two forms of the shuffle method: One takes
only a List object; the other takes both a List object and a
Random object. According to Javadoc, this second form will
"randomly permute the specified list using the specified source of
randomness." In other words, the Random object passed will be used
as a basis for determining how the List gets shuffled.
Our test would create a Random object with a specific seed. (See the sidebar, "Random Number Sequences Really Aren’t Random.") It would somehow inject that Random object into the Deck object being tested. The Deck object would use this Random object as the basis for shuffling.
- We could then write code in the test to expect cards to appear in a specific order as they’re dealt off the top of the deck. But how would we know what these cards should be? Simple: Run the test once, expecting it to fail. When it runs, note the cards that get dealt. Use this knowledge and plug these cards back into the test specifics. So, for a given random sequence, cards a, b, and c should be dealt.