In the last blog I discussed the project structure and the unit testing frameworks I will use. In this blog I will begin building out the class and tests based on our business requirements.
Players Name
I need to enter a player’s name and have the ability to get it. I always start our defining the class before writing the first test. I know others tell you to start with the failing test but to me it makes no sense to not at least stub out the bare class.
public class Scorer { private string _player; public Scorer(string Player) { this._player = Player; } public string Player { get { return _player; } } }
We will pass in our player’s name through the constructor and create a getter to retrieve it. Our first test looks like this:
[TestFixture] public class Scorer_Tests { [Test] public void ShouldReturnPlayersName() { var _player = "Denham"; var _scorer = new Scorer(_player); Assert.That(_scorer.Player, Is.EqualTo(_player)); } }
We mark our class with the TestFixture attribute and the first test with the Test attribute. This enables the Test Explorer to locate the test methods within the assembly. We instantiate our Scorer class and then assert that the player returned is the same as the player name entered. A nice simple one to begin. If we run our tests from the Test Explorer window (CTRL+E, T) by clicking on Run All, we can see it passes.
Adding a Frame Score
So now we need to add the players score for one frame. A player can throw either 1 ball or 2 balls so we need a method that takes two parameters. We also need to think about how we are going to hold the scores in our class. We could just calculate the score as each frame is completed, catering for spares and strikes but one of our other business requirements is to print out the game scorecard. So actually we need to keep track of the whole game. So really we don’t need to calculate the score after each frame. We can calculate the score in one go following the scorecard. We need the ability to hold 2 x 12 values.
I have written a test as follows:
[Test] public void EnterValidFrameScore() { var _player = "Denham"; var _scorer = new Scorer(_player); _scorer.FrameScore(1, 2); }
The only problem is, what do I assert on? What we should do is validate that the two values entered must add up to a value between 0 and 10 and if they don’t throw an exception.
[Test] public void EnterValidFrameScore() { var _player = "Denham"; var _scorer = new Scorer(_player); Assert.That(() => _scorer.FrameScore(10, 10), Throws.TypeOf()); }
So now we can test if an ArgumentException is thrown. We enter a score of 10 and 10 which are invalid. Let’s add our code to test for this.
public void FrameScore(int Bowl1, int Bowl2) { var _score = Bowl1 + Bowl2; if (_score 10) throw new ArgumentException("Invalid scores entered"); _frameScores.Add(new Tuple(Bowl1, Bowl2)); }
Notice I created a tuple for our two input parameters and adding it to a list in order for us to keep track of the scores. If I run the test it passes.
Testing for Multiple Invalid Frame Scores
It would be tedious to write one test for each input validation. NUnit has data driven testing which allows us to pass multiple values to a single test where each value is testing individually. Look at the amended test below.
[TestCase(0, 11)] [TestCase(12, 1)] [TestCase(6, 6)] public void EnterInvalidFrameScores(int Bowl1, int Bowl2) { var _player = "Denham"; var _scorer = new Scorer(_player); Assert.That(() => _scorer.FrameScore(Bowl1, Bowl2), Throws.TypeOf()); }
I have renamed the test to be more appropriate and now added the TestCase attributes to test for 3 different entered values. Our test method now has two parameters which are referenced in the code. In the Test Explorer window notice we get one entry per test case.
You may think that we have covered all test cases but what if I enter -5 and 8?
Our tests failed. If you look back to our code, I am adding the values and then testing the result is between 0 and 10. Let’s fix that.
public void FrameScore(int Bowl1, int Bowl2) { if (Bowl1 10) throw new ArgumentException("Bowl1 must be between 0 and 10"); if (Bowl2 10) throw new ArgumentException("Bowl2 must be between 0 and 10"); var _score = Bowl1 + Bowl2; if (_score 10) throw new ArgumentException("Invalid scores entered"); _frameScores.Add(new Tuple(Bowl1, Bowl2)); }
Now I am checking the input range as well as the total. If we run our tests again, the tests complete successfully.
Testing for Multiple Valid Frame Scores
Let’s add a test for multiple valid scores. Since an exception is thrown for invalid scores, as long as the code does not throw an exception, the data is good. So we do not have to assert for no exception.
[TestCase(0, 0)] [TestCase(0, 10)] [TestCase(10, 0)] [TestCase(5, 5)] public void EnterValidFrameScores(int Bowl1, int Bowl2) { var _player = "Denham"; var _scorer = new Scorer(_player); _scorer.FrameScore(Bowl1, Bowl2); }
If we run our tests again, the tests complete successfully.
We will continue adding more functionality in our next blog.