From Event to Action: Accelerate Your Decision Making with Real-Time Automation
Bowling Game Kata in C# Adapted
1. Bowling Game KataAdapted for C# and nUnit By Dan Stewart (with modifications by Mike Clement) dan@stewshack.com mike@softwareontheside.com
2. Scoring Bowling. The game consists of 10 frames as shown above. In each frame the player has two opportunities to knock down 10 pins. The score for the frame is the total number of pins knocked down, plus bonuses for strikes and spares. A spare is when the player knocks down all 10 pins in two tries. The bonus for that frame is the number of pins knocked down by the next roll. So in frame 3 above, the score is 10 (the total number knocked down) plus a bonus of 5 (the number of pins knocked down on the next roll.) A strike is when the player knocks down all 10 pins on his first try. The bonus for that frame is the value of the next two balls rolled. In the tenth frame a player who rolls a spare or strike is allowed to roll the extra balls to complete the frame. However no more than three balls can be rolled in tenth frame.
10. Begin. Add a test fixture named BowlingGameTests to the project usingNUnit.Framework; namespaceBowlingGameKata { [TestFixture] publicclassBowlingGameTests { [Test] publicvoidGutterGameTest() { } } }
11. The first test. [Test] publicvoidGutterGameTest() { Game g = new Game(); }
12. The first test. namespaceBowlingGameKata { publicclassGame { } } [Test] publicvoidGutterGameTest() { Game g = new Game(); }
13. The first test. [Test] publicvoidGutterGameTest() { Game g = new Game(); } publicclassGame { }
14. The first test. [Test] publicvoidGutterGameTest() { Gameg = newGame(); for(int i = 0; i < 20; i++) { g.Roll(0); } } publicclassGame { }
15. The first test. public class Game { publicvoid Roll(int p) { thrownewSystem.NotImplementedException(); } } [Test] publicvoidGutterGameTest() { Gameg = newGame(); for(int i = 0; i < 20; i++) { g.Roll(0); } }
16. The first test. [Test] publicvoidGutterGameTest() { Gameg = newGame(); for(int i = 0; i < 20; i++) { g.Roll(0); } Assert.That(g.Score(), Is.EqualTo(0)); } public class Game { publicvoid Roll(int p) { thrownewSystem.NotImplementedException(); } }
28. The Third test. - ugly comment in test. publicclassGame { privateint score; publicvoid Roll(int pins) { score += pins; } publicint Score() { return score; } } [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); Assert.That(g.Score(), Is.EqualTo(16)); } tempted to use flag to remember previous roll. So design must be wrong. Failed Assert.AreEqual Expected:<16>. Actual:<13>
29. The Third test. - ugly comment in test. Roll() calculates score, but name does not imply that. publicclassGame { privateint score; publicvoid Roll(int pins) { score += pins; } publicint Score() { return score; } } [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); Assert.AreEqual(16, g.Score); } Score() does not calculate score, but name implies that it does. Design is wrong. Responsibilities are misplaced. Failed Assert.AreEqual Expected:<16>. Actual:<13>
32. The Third test. - ugly comment in test. publicclassGame { privateint[] rolls = newint[21]; privateint currentRoll; publicvoid Roll(int pins) { rolls[currentRoll++] = pins; } publicint Score() { int score = 0; for (int i = 0; i < rolls.Length; i++) { score += rolls[i]; } return score; } } [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.That(g.Score(), Is.EqualTo(16)); }
33. The Third test. - ugly comment in test. [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); Assert.That(g.Score(), Is.EqualTo(16)); } publicclassGame { privateint[] rolls = newint[21]; privateint currentRoll; publicvoid Roll(int pins) { rolls[currentRoll++] = pins; } publicint Score() { int score = 0; for (int i = 0; i < rolls.Length; i++) { score += rolls[i]; } return score; } } Failed Assert.AreEqual Expected:<16>. Actual:<13>
34. The Third test. - ugly comment in test. publicint Score() { int score = 0; for (int i = 0; i < rolls.Length; i++) { // spare if(rolls[i] + rolls[i+1] == 10) { score += ... } score += rolls[i]; } return score; } [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); Assert.That(g.Score(), Is.EqualTo(16)); } This isn’t going to work because i might not refer to the first ball of the frame. Design is still wrong. Need to walk through array two balls (one frame) at a time. Failed Assert.AreEqual Expected:<16>. Actual:<13>
35. The Third test. - ugly comment in test. publicclassGame { privateint[] rolls = newint[21]; privateint currentRoll; publicvoid Roll(int pins) { rolls[currentRoll++] = pins; } publicint Score() { int score = 0; for (int i = 0; i < rolls.Length; i++) { score += rolls[i]; } return score; } } [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.That(g.Score(), Is.EqualTo(16)); Assert.Inconclusive(); }
36. The Third test. - ugly comment in test. publicint Score() { int score = 0; inti = 0; for (int frame = 0; frame < 10; frame++) { score += rolls[i] + rolls[i + 1]; i+= 2; } returnscore; } [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.That(g.Score(), Is.EqualTo(16)); Assert.Inconclusive(); }
37. The Third test. - ugly comment in test. [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); Assert.That(g.Score(), Is.EqualTo(16)); } publicint Score() { int score = 0; int i = 0; for (int frame = 0; frame < 10; frame++) { score += rolls[i] + rolls[i + 1]; i += 2; } return score; } Failed Assert.AreEqual Expected:<16>. Actual:<13>
38. The Third test. - ugly comment in test. publicint Score() { int score = 0; inti = 0; for (int frame = 0; frame < 10; frame++) { // spare if (rolls[i] + rolls[i + 1] == 10) { score += 10 + rolls[i + 2]; i += 2; } else { score += rolls[i] + rolls[i + 1]; i += 2; } } return score; } [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); Assert.That(g.Score(), Is.EqualTo(16)); }
39.
40. ugly comment in conditional.publicint Score() { int score = 0; int roll = 0; for (int frame = 0; frame < 10; frame++) { // spare if (rolls[roll] + rolls[roll + 1] == 10) { score += 10 + rolls[roll + 2]; roll += 2; } else { score += rolls[roll] + rolls[roll + 1]; roll += 2; } } return score; } [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); Assert.That(g.Score(), Is.EqualTo(16)); }
41. The Third test. - ugly comment in test. publicint Score() { int score = 0; int roll = 0; for (int frame = 0; frame < 10; frame++) { if(IsSpare(roll)) { score += 10 + rolls[roll + 2]; roll += 2; } else { score += rolls[roll] + rolls[roll + 1]; roll += 2; } } returnscore; } privateboolIsSpare(int roll) { returnrolls[roll] + rolls[roll + 1] == 10; } [Test] publicvoidOneSpareTest() { g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); Assert.That(g.Score(), Is.EqualTo(16)); }
42. The Third test. [Test] publicvoidOneSpareTest() { RollSpare(); g.Roll(3); RollMany(17, 0); Assert.That(g.Score(), Is.EqualTo(16)); } privatevoidRollSpare() { g.Roll(5); g.Roll(5); } publicint Score() { int score = 0; int roll = 0; for (int frame = 0; frame < 10; frame++) { if(IsSpare(roll)) { score += 10 + rolls[roll + 2]; roll += 2; } else { score += rolls[roll] + rolls[roll + 1]; roll += 2; } } returnscore; } privateboolIsSpare(int roll) { returnrolls[roll] + rolls[roll + 1] == 10; }