Recently I have been learning Test Driven Development. TDD as its known is a practice for writing and testing your code. It relies on the developer to write very small cycles of code in repetition.
So after writing the code kata for the bowling game in PHP using TDD, I decided to do a few more code katas. After this I then came up with own code kata for the Blackjack card game using TDD.
You can download the Blackjack code kata on Github.
Background on TDD
Before I start explaining about the codebase, I want to just get a brief overview of TDD. The purpose of TDD is that you the developer can test your codebase and feel confident in making any changes to codebase. It is also helps you make better design decisions and encourage you to write fast small cycles of code which can be tested.
The red green refactor method is the method or technique that is used for TDD. First you must write the test. Then you write code so it will fail(red). Then you write code so that it will pass (green). Finally you then refactor your code. This is known as a cycle in which TDD depends on and what I have used to create the code kata.
So as I was explaining earlier in the article I came up with my own kata for the card game Blackjack. The purpose of the code for this kata was to determine who won the game of Blackjack using TDD.
As Blackjack seems to have a few variations of the rules these are the rule I used for my code kata.
- Numeric cards are scored one to nine.
- Jack, Queen, King are scored as ten.
- Ace can be scored as either one or eleven.
- The person with the nearest score to 21 wins.
- If someone scores over 21 they are disqualified.
- Anyone with a score over 21 is disqualified.
- Anyone using over 5 cards is disqualified.
- If the two players have the same score, then the person with the least amount of cards used wins.
- If the two players have the same score and the same amount of cards, then its a draw.
- However if the score is the same between a dealer and a player, then the dealer wins no matter how many cards the dealer uses.
- The only exception is if the player has the perfect score (21) and uses only two cards.
- Finally if the dealer has the perfect score (21) and uses two cards and the player has the same score (21) and uses two cards, then the dealer wins.
As you can see the rules can get quite complicated. We are going to use TDD for writing our code.
As mentioned above, I have added the code to Github. The purpose of this repository is to show the different stages of my codebase while using TDD and then to show the final result of TDD.
But before I start going through the tests I would like to explain the structure of this repository.
This folder contains the final code of the repository. This is result of all the different stages with creating the tests using TDD.
The folder tests contain the tests. They are all in one file.
The folder contains all the versions of the code. Each time I made a change to the file (red, green, refactor) I saved the file by its name, increment and status.
This was so that I could document the process in which I used the red, green, refactor technique. I also saved the files into subfolders of the test number. This is so that I could revise what I did at what stage or test.
The number of the sub folder refers to the position of the test in the PHPUnit test file.
It should be also noted that I used PHPUnit for testing my code. PHPUnit is a testing framework for PHP for testing your code. The PHPUnit configuration file is in the root of the repository.
Writing the tests
The first part was that I needed to figure out what I was testing and what tests I needed to write. After writing down the tests on paper, these are the tests I came up with for testing the codebase.
- 2 Players no picture cards who wins
- 2 Players with picture cards who wins
- 2 Players, one with score over 21, who wins
- 2 Players with ace and score over 21, who wins
- 2 Players with one user over 5 cards, who wins
- 2 Players over 21
- 2 Players with same score and same cards
- 2 Players with same score and one with less cards
- 1 Player and Dealer with same score and dealer wins
- 1 Player with perfect score and dealer with same score, player wins.
- 1 Player with perfect score and dealer with perfect score. Dealer wins.
After spending some time analyzing the tests after writing the code I decided to separate them into two parts, tests 1 to 5 and tests 6 to 11. The first part (tests 1-5) deals with calculating the card score. The second part (tests 6 to 11) deals with calculating who wins.
As I go through the tests you will see that I eventually separate my code into 3 classes, Blackjack, Card Score and Winner.
Blackjack is the main class that calls the other classes for calculating the card score and then determining the winner.
Part 1: Calculating the card score
Test 1: test_three_players_no_picture_cards()
The first test was to determine who would win between 2 players with no picture cards (Jack, Queen, King & Ace). I just wanted to first figure out who won with no picture cards.
As you can see in the refactored file in Test 1, I calculate the score by looping through the cards and adding them to a score variable. I then looped calculateScore().
Then in the method getWinner() I loop through each player and if their score was the higher then the variable $highestScore. If it is then I assign the player to the variable $winner and return the variable $winner after the loop.
Test 2: test_three_players_with_picture_cards()
The next test is to see what happens if one player has picture cards.
At this point I needed to change the functionality for calculating the score. So instead of converting the card number to a integer I pass the number as a variable into a method for calculating the card score getCardScore(). The card score method maps the number to an array with the score. The card number is the key in the variable and the score is the value of that key.
Finally I then decide that this responsibility should be its own separate class and I create the class CardScore and move the related functionality into this file.
Test 3: test_two_players_one_over_21()
The next test is to see if a player score exceeds the limit 21, then the other player should win.
So I add a method called getScoreLimit() which returns the limit. Then in the method for calculating the score calculateCardScore() I return the result as 0 if the total card score exceeds the limit.
Test 4: test_two_players_one_with_two_aces()
The next test sees if the score is over 21 and a user has an ace, it should be converted to 1. At the moment the class CardScore is setting the score for the ace as 11 so if you had Jack, Queen and a Ace the score would be 31. As we know from the rules of Blackjack. The score would be 21 as the score for the ace card would be 1 instead of 11.
So I create two method, one to check to see if the cards have aces – hasAces() and the second method to convert ace scores into ones instead of eleven – setAceScoresToOne(). These methods are then only called if the user has exceeded the score limit.
Test 5: test_two_players_one_with_two_over_five_cards()
The next test is to determine whether a player with a higher score but over the card limit of 5 beats another player with a lower card score and under the card limit.
So what I have done here is created a method for checking the card limit exceededCardLimit() and in the method for calculating the score I just check at the start of the method if the user has exceeded the card limit and if they have I return the score as 0.
Part 2: Calculating the winner
So in part 1, you can see that we are mainly concerned with calculating the card score but the next few tests relate to the rules with determining who wins when two players or a player and a dealer has the same score.
Test 6: test_two_players_with_both_over_21()
So currently we are only returning one winner as there is always one winner. But what happens if both users are over 21. We should return false.
So what we do is set the variable $winner to false and then if there is no winner the variable will return false by default.
Test 7: test_two_players_with_same_score_same_amount_of_cards()
So at this point we have only one winner in each test scenario. But what happens if its a draw. We need to return both players.
So the first thing we do is alter the getWinner() method and turn the variable $winner into an array. We then after looping through the players check the amount of winners.
If there is no winners we return false, if there is one winner we then return the first winner in the array. Finally if there is more than one winner, we then return the array of winners.
At this point we separate the related functionality for determining the winner into its own class Winner to separate the concerns of the class.
Test 8: test_two_players_with_same_score_one_with_less_cards()
The next test is to deal with when 2 players have the same score and one has less cards. So in the class Blackjack we add a key ‘cards’ to the array for the player and the value of the key is the array of the players cards.
Then in the method getWinner() we then loop through all the winners and calculate their card count (Winner-3-green.php).
After this we need to refactor the code quite a bit as the method getWinner() has got quite complex. So the first thing we do is remove the variable $lowestCardScore and set that as a property of the class (Winner-4-refactor.php) with a getter and setter method.
We then remove the variable $highestScore into its own property and a getter and setter for this property(Winner-5-refactor.php).
We then finally move the functionality for checking the lowest card score to the method determineWinner() and check when the player has the joint highest score, that the player also has the lowest card score. If the player does not have the lowest card score they will not be added to the array of winners.
Test 9: test_player_dealer_same_score_wins()
The final part is now to introduce the dealer and determine what happens when the dealer and a player has the same score.
So when the player has the same score we check if its the dealer or if the dealer is a winner. If the player is the dealer we remove all winners as the dealer can only be the winner.
Test 10: test_player_with_perfect_score_dealer_same_score_player_wins()
The only exception when the player is the winner when the player and the dealer has the same score is when the player has the perfect score.
The perfect score is 21 and with 2 cards. So we add a method for checking does the player has the perfect score and then a property which is an array for perfect score players. If the player has a perfect score we add them to the array.
The next part is to check at the start of the functionality of determineWinner() if there is any perfect scores. If there is a perfect score and the player does not have a perfect score we don’t add them to the array of winners.
Test 11: test_player_and_dealer_with_perfect_score_dealer_wins()
Finally this test does not require any more functionality as when the dealer has the perfect score it will remove any other players with the perfect score.
I did really enjoy coding using TDDand creating the code kata’s. I can definitely see the benefits of TDD and also practising TDD with code kata’s. I would love to hear your comments and feedback on anything mentioned in this article from TDD to code kata to the code I put up on Github.