Intro to Test Driven Development (TDD) Part 2

In this article we’re going to tackle the extra credit portion of the kata we worked on in Part 1 (feel free to take a step back to that article if you haven’t yet read it). We’re going to continue where Part 1 left off, so make sure your code is in a state that resembles that given at the end of Part 1, or download it here.

The Kata

In Part 1 we covered everything up to the extra credit portion (we ended with verifying the last example roll of [3,4,5,3,3] = 350 points). In this part, we are going to work on the extra credit section of the kata. I’ve posted the entire kata below for reference:

Instructions

Greed is a press-your-luck dice rolling game. In the game, the player rolls the dice and tries to earn as many points as possible from the result. For the purposes of this kata, we will just be scoring a single roll of five dice (but see Extra Credit below).

Write a scoring method that calculates the best score based on a given roll using the following set of scoring rules. Each die can only be scored once (so single die scores cannot be combined with triple die scores for the same individual die, but for instance four 5s could count as 1 Triple (500) and 1 Single (50) for a total of 550.

  • A single one (100 points)
  • A single five (50 points)
  • Triple ones [1,1,1] (1000 points)
  • Triple twos [2,2,2] (200 points)
  • Triple threes [3,3,3] (300 points)
  • Triple fours [4,4,4] (400 points)
  • Triple fives [5,5,5] (500 points)
  • Triple sixes [6,6,6] (600 points)

Example Scores

  • [1,1,1,5,1] = 1150 points
  • [2,3,4,6,2] = 0 points
  • [3,4,5,3,3] = 350 points

Extra Credit

Modify your scoring method to support the following rules:

  • The player may throw 1 to 6 dice at a time.
  • Four-of-a-Kind [2,2,2,2]: Multiply Triple score by 2 (e.g. 4 2s = 400)
  • Five-of-a-Kind [2,2,2,2,2]: Multiply Triple score by 4 (e.g. 5 2s = 800)
  • Six-of-a-kind [2,2,2,2,2,2]: Multiply Triple score by 8 (e.g. 5 2s = 1600)
  • Three Pairs [2,2,3,3,4,4] (800 points)
  • Straight [1,2,3,4,5,6] (1200 points)

Step 1

Let’s jump right into things. The first thing we want to test is that a player can throw anywhere from 1 to 6 dice at a time. To test that, I’m going to write a test that checks for an exception when the number of dice thrown is less than 1, or more than 6:

I decided to use a Theory for this test so that I can test multiple cases. First, I test the result when no dice are thrown, then 1 – 6 dice, and finally a roll with 7 dice thrown. I then use xUnit’s Record.Exception method to check if an exception was thrown, and compare it to my expected outcome.

If you run this test now, you should have 2 failing tests:

The two tests that fail are the ones that check for more than 6 dice thrown, and no dice thrown. Let’s update our CalculateScore method to make it pass. I added 2 lines of code at the start of the method to accomplish this:

Run your tests again – they should all pass.

Step 2

Next, let’s write a test that checks the scoring of a 4-of-a-kind roll. Rolling a four-of-a-kind should return the score of rolling three-of-a-kind times 2 (e.g. 4 ones = 1000, 4 twos = 400, 4 threes = 600, etc…). Again, I will use a Theory/InlineData test for this

Run the test, and you should see a fail result similar to this:

Let’s think about what we need to do to create a passing test. We’ll need a new method to calculate 4-of-a-kind rolls, which should return the score of rolling a triple multiplied by two. Since we’re also rolling one more die than the triple score, we’ll also need to reduce the die count by one more. Try and come up with something on your own first before you look ahead. I came up with something like this:

You may come up with something different/better, but here I am first checking the case of rolling 4 ones, since those are scored differently. I then store the resulting score from the ScoreTripleOnes() method into a variable, and then return that result times 2. I then do the same for rolling 2 through 6. Since we’re only rolling a max of 6 dice, 4-of-a-kind can only happen up to 1 time, so I immediately return the score once a match is found.

In order to use this method, we’ll have to update our CalculateScore() method to include scoring 4-of-a-kind rolls. Let’s update CalculateScore() like so:

Now let’s run our tests:

It looks like all of our new tests passed, but three old tests have now failed! If you look closer at the tests that failed, you should notice that those tests have 4-of-a-kind rolls in them, therefore the scoring and test names need to be updated. These tests were great before the new rules came into play, but we’ll have to fix them.

Step 3

The first test that failed is Returns1100WhenOneOneOneOneRolled. On the old rules, before 4-of-a-kind was a thing, the score should have been 1100. Now, it should be 2000 (remember, 4-of-a-kind is the triple score times 2, so triple ones = 1000, times 2 = 2000). Let’s update it:

The second test that failed was Returns1150GivenOneOneOneFiveOne. Again, we have a 4-of-a-kind in here, plus an extra 5, so the new score should actually be 2050 (2000 for the 4-of-a-kind, plus 50 for rolling a 5). Let’s update that test as well:

The last test that failed was Returns600GivenAllFives. That score should now return 1050 (quad 5s = 1000, plus 50 for rolling a 5):

Run your tests again, and they should all now pass!

When you take a TDD approach to development, you deal with the rules as they come. Sometimes a side effect of this is having to re-do some of the tests, as you’ve just seen.

Step 4

Let’s move on to scoring 5-of-a-kind. The test will essentially be the same as scoring 4-of-a-kind, with the exception of the test name, the expectedScore values, and adding an additional dieValue to the CalculateScore call:

If we run the tests, they should all fail (and they do – I’ll spare you the screenshot). To make them pass we can essentially do the same thing we did for scoring 4-of-a-kind. We’ll have to tweak it a little bit though, instead of deducting 1 additional dieCount, we’ll deduct 2, and instead of returning the score times 2, we’ll return the score times 4:

We’ll also update CalculateScore to call the new method:

Run the tests now, and we’ll see something familiar. All the new tests pass, but an old one fails:

Returns1050GivenAllFives (the one we just fixed in Step 3) fails. Again, this is because the scoring rules have now changed. Since this test is actually covered in the one we just wrote (it’s scoring 5 fives), we can just delete it.

Step 5

Let’s look at the next feature: scoring 6-of-a-kind. By now, we should know what the test will look like, so let’s write that:

We can do a few things to make this test pass. The easiest thing to do is essentially copy the same method we used for scoring 5-of-a-kind, and updating the dieCount reduction, and the score multiplier:

And update the CalculateScore() method:

Run your tests. Spoiler: they all pass! At this point, you should start to recognize that your code is getting pretty repetitive. The scoring model for 4, 5, and 6-of-a-kind is repetitive and based on the score of 3-of-a-kind. Try and refactor it a little bit if you want. There may be many ways to do this, but here is what I chose to do:

Then update the CalculateScore() method (my new method ScoreMoreThanThreeOfAKind() replaced ScoreFourOfAKind(), ScoreFiveOfAKind() and ScoreSixOfAKind(), so you can just delete them:

After refactoring, all of my tests still pass. I’ll consider that successful, and move on to the next test!

Step 6

Next up on the list is calculating triple pairs ([1, 1, 2, 2, 3, 3], [2, 2, 3, 3, 4, 4], etc…). The score for any triple pair is 1800 points. Here’s my test, again using a theory:

Run your tests now, and all the new ones fail. Here’s how I made it pass:

And again, update CalculateScore():

Give it a run, they should all pass! Let’s get on to our final extra credit piece!

Step 7

The last piece is checking for a straight and returning a score of 1200 points. Let’s start with the test. There’s only one condition for this, so we can switch back to using a Fact:

To make this work, I think I’ll use a method similar to step 6. I’ll just check that the count for each die rolled is equal to 1, and then I’ll set the count to 0 so that the dice aren’t counted in any other scoring methods. I thought I would get a little creative and use LINQ (Enumerable.All) for this:

To my excitement, all tests passed!

The last thing I’ll do is change the access modifiers on all of the methods except CalculateScore() to private…mainly because I didn’t catch it until now. Since all of those methods are internal to the Game class, we should design it so that only the Game class can use them.

This kata is now complete (or is it??)! In Part 3 of this tutorial I will discuss refactoring even further using something called a Rules Engine / Rules Pattern! Stay tuned!

Final Result

The final result of our code is below (download it here):

Test Class

Game Class

About the Author

FranksBrain

Frank is an experienced IT director of 15+ years who has been working with computers since the age of 13. While hardware and networking were the base of his career, he also enjoys programming, and is currently focused on .NET Core, Angular, and Blazor. He enjoys constantly learning something new, and helping others do the same.

Leave a Reply

Your email address will not be published.