- Adding a Game Class, Test-First
- Limiting the Class with Tests
- At Last, Here Comes the Code
At Last, Here Comes the Code
Okay, we still haven’t made much progress. I’m going to stop writing this text for a bit, and just crank out code. I’ll turn on the timer so we have a rough idea of how productive we are using TDD.
Tic tock...
Twelve minutes later, I’ve built some of the support for dealing hole cards. At the start of each Texas Hold ’Em hand, each player is dealt two cards down; these are known as hole cards. Cards are always dealt one at a time in clockwise order, starting with the player to the left of the dealer (marked by a button).
Before, we represented players by just a name. Dealing cards to players now requires a Player class to capture each player’s hand.
The twelve minutes of code presented in Listings 6–9 (GameTest, Game, PlayerTest, and Player) was all written test-first, of course, and was all refactored for readability and succinctness.
Listing 6 GameTest.
package domain; import java.util.*; import junit.framework.*; public class GameTest extends TestCase { private Game game; private Deck deck; protected void setUp() { game = new Game(); deck = game.deck(); } public void testCreate() { assertPlayers(); } public void testAddSinglePlayer() { final String name = "Jeff"; game.add(new Player(name)); assertPlayers(name); } public void testAddMaximumNumberOfPlayers() { for (int i = 0; i < Game.CAPACITY; i++) game.add(new Player("" + i)); assertPlayers("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); } public void testDealHoleCards() { Player player1 = new Player("a"); Player player2 = new Player("b"); game.add(player1); game.add(player2); game.dealHoleCards(); assertHoleCards(deck, player1, 2); assertHoleCards(deck, player2, 2); } private void assertHoleCards(Deck deck, Player player, int expected) { List<Card> holeCards = player.holeCards(); assertEquals(expected, holeCards.size()); assertCardsDealt(deck, holeCards); } private void assertCardsDealt(Deck deck, List<Card> cards) { for (Card card: cards) assertFalse(deck.contains(card.getRank(), card.getSuit())); } private void assertPlayers(String... expected) { assertEquals(expected.length, game.players().size()); int i = 0; for (Player player: game.players()) assertEquals(expected[i++], player.getName()); } }
Listing 7 Game.
package domain; import java.util.*; public class Game { public static final int CAPACITY = 10; private List<Player> players = new ArrayList<Player>(); private Deck deck = new Deck(); public List<Player> players() { return players; } public void dealHoleCards() { for (int i = 0; i < 2; i++) for (Player player: players) player.dealToHole(deck.deal()); } public void add(Player player) { players.add(player); } // needed for testing Deck deck() { return deck; } }
Listing 8 PlayerTest.
package domain; import java.util.*; import junit.framework.*; public class PlayerTest extends TestCase { private final static String NAME = "x"; private Player player; protected void setUp() { player = new Player(NAME); } public void testCreate() { assertEquals(NAME, player.getName()); } public void testHoleCards() { Card card1 = new Card(Rank.ace, Suit.hearts); Card card2 = new Card(Rank.three, Suit.diamonds); player.dealToHole(card1); player.dealToHole(card2); List<Card> holeCards = player.holeCards(); assertCards(holeCards, card1, card2); } private void assertCards(List<Card> holeCards, Card... cards) { assertEquals(cards.length, holeCards.size()); int i = 0; for (Card card: cards) assertEquals(card, holeCards.get(i++)); } }
Listing 9 Player.
package domain; import java.util.*; public class Player { private String name; private List<Card> holeCards = new ArrayList<Card>(); public Player(String name) { this.name = name; } public String getName() { return name; } public List<Card> holeCards() { return holeCards; } public void dealToHole(Card card) { holeCards.add(card); } }
I think I hear you saying, "Twelve minutes? I could have coded what’s in Player and Game in about six minutes." Maybe you could have, and maybe so could I. But you wouldn’t have ended up with comprehensive tests that demonstrate to other developers how to use the classes. You also might have made a simple mistake along the way—one that quickly bloated your time past twelve minutes while you scratched your head figuring out what went wrong.
I drove out those twelve minutes of code like a slow-and-steady tortoise beating out the sprinting hare. I can keep up this consistent pace for hours, days, months, getting feedback every few seconds that I’m on the right track. Many times when I’ve sprinted, I’ve had to retrace my route when I discovered that I wasn’t where I thought I should be. I used to program in bursts of a few hours’ worth of code without testing feedback. Often, I would find a defect that I had unwittingly introduced in the first half-hour of coding. I would then spend as much time unraveling that clump of code as I had spent keying it in the first place.
I’m not sure that twelve minutes qualifies as "banging out code." I’m going to go offline now and spend a bit more time, maybe an hour or two, really trying to put some meat into the Texas Hold ’Em game. I’ll summarize in the next installment what I produce.
Next segment: "Adding Some Bulk." Meanwhile, here’s the code (source.zip) we’ve built in this installment.