The Bowling Game Kata is a very well known coding exercise created by Robert C. Martin geared towards practicing Test Driven Development (TDD). My cohort Russell Blake from G Systems was reading through Robert Martin's fantastic book, "Agile Software Development" where he came across the Bowling Game Kata and had the brilliant idea of recreating it in LabVIEW and blogging about it. We decided to each go through the kata with a different unit test tool and blog about our experience, I will be going through the kata using the LabVIEW Unit Test Framework Toolkit (UTF), Russell will follow up with a post that goes through the kata with JKI's VI Tester. Start by downloading Robert Martin's Bowling Game Kata PowerPoint slide deck here to use as a companion to this blog post. The goal of the kata is to use TDD to write code that models the scoring system of 10-pin bowling. The first few slides of the presentation outline the rules of 10-pin bowling and diagram out a class hierarchy that models the rules. While Robert Martin does create a UML diagram upfront he does not design his code to match it, the UML is used as a guideline not as a blueprint. This is an important point, as you will see, Robert Martin allows TDD to guide the design of the code as opposed to the up front design that is the UML class diagram. BeginWe begin the kata by creating a new LabVIEW project named Bowling Game. Because TDD follows the Red-Green-Blue (Fail-Pass-Refactor) workflow, our first goal will be to write a failing test. In the kata the first failure is encountered when the test runner is executed without any unit tests. By clicking on the "Run Unit Tests" button in the toolbar of the Project Explorer we can execute the UTF without having any tests in place. First test - FailThe first test we create is intended to test the simplest bowling game, a gutter game. A gutter game is a game in which every ball you throw goes down the gutter, resulting in a score of zero. Per the kata our first step is to create a test case called testGutterGame. By right-clicking on the target we can select New>>Unit Test. We do not yet have any production code in our project, and we shouldn't. Our first goal is to write the simplest failing test that we can, and then write the simplest production code needed to make that failing test pass. We can run this first test using the UTF to get our first fail. This first failure stems from the fact that the UTF needs a VI to test, so lets fix it. First Test - PassIn the kata the first thing that the first test does is construct a Game class. The first thing we will do is create a BowlingGame class within our project. With the UTF we will need to create a User-Defined Test Template.vit in order to test our BowlingGame class. The User-Defined Test Template allows us to write some xUnit style test code that the UTF can execute. Test code for the construction of a BowlingGame class would look like this. This test is kind of weird because I have no good way of basing my assertion on the presence or absence of the class. Compare this to what Robert Martin does on slides 12-14 of his kata slide deck. Now that I have created my testGutterGame.vi from the User-Defined Test Template I need to associate it with my test case. I can right click on the testGutterGame.vi and choose Unit Test>>New User-Defined Test to create a new test case that uses testGutterGame.vi. I am going to go ahead and delete the untitled.lvtest from the project at this point as I replaced it with the testGutterGame test case. It is important to note that while practicing TDD it is expected that unit tests will be deleted when they are no longer needed. I can now execute the UTF and run my testGutterGame test class and it should pass. First Test - RefactorI don't really have anything to refactor at this point. First Test - FailThe goal of the first test is that it should test a game of bowling where every ball thrown goes into the gutter, ending with a score of zero for the completed game. The test does not yet accomplish this, so lets add some code. The test code above has us rolling the ball twenty times, which makes sense because ten frames with two rolls per frame is twenty rolls. In the kata we see that this test code is able to reference a Roll method that does not yet exist in the production code project, which causes a failure. I am not able to have the test code reference a Roll method that does not yet exist and so am unable to trigger the same failure. In order to make the test case fail I needed to create the shell of a roll method that I could call in my test code. Within the test code I can now call the roll method within our for loop, passing a zero in for each of the twenty rolls. Before I can assert a zero value for the score for a gutter game I will need to create a Score method. Because the Score method is incomplete I have hard-coded the output to return -1. I can now use the Score method in my test code, and assert a value of zero as the output for my testGutterGame test case. I can now run my test case, and I expect that it will fail. Now I need to make this test pass. First Test - PassBecause I am doing TDD my goal is to make this test pass in the simplest way possible. In this case the simplest way to make the testGutterGame test case pass is to update the Score method to output a zero. Now I can run the test case using the UTF and get my passing result. First Test - RefactorI still don't have much code that I can refactor, however I really don't need the case structures that were scripted into my Roll and Score methods on creation. I can refactor these methods to remove the case structures as well as the error in controls and error out indicators. By removing these unnecessary nodes from the block diagrams of my methods I have cleaned up the code and raised my code coverage up to 100%. These refactoring cycles also provide the opportunity improve documentation within the code, to create better icons for our methods, and even rename our methods if necessary. Take advantage of the refactor cycle to leave the code cleaner than you found it. Second Test - failNow that I have a passing and refactored first test for rolling a gutter game I can move on to the next simplest test case, rolling a game of all ones. I start by creating a new User-Defined VI from template and populate it with the code necessary to roll all ones. I can create a User-Defined Test from the testGutterGame.vi and run my tests through the UTF. You can see that my testAllOnes test case is failing and that my testGutterGame is still passing. Now it's time to make my testAllOnes test case pass. Second Test - PassIn order to make the testAllOnes test case pass I will need to add some actual code to my Roll and Score methods, and I will need to up date my BowlingGame class data to include a variable for score. The first thing I will do is add an integer named Score to my BowlingGame class data. Now that my BowlingGame class has a score variable I can update my Roll method to add the number of pins knocked down per roll to my current score. This algorithm won't account for spares and strikes but it doesn't need to yet, it just needs to handle gutter games and single pin games. I can also now pass the actual score out of the Score method and it will no longer need to be hard coded to zero. Second Test - Refactor Now that I have two passing test cases I can take a look at my code and refactor. Something to keep in mind while practicing TDD is that refactoring the test code is as important as refactoring the production code. Taking a look at the test code I see an opportunity to refactor. Both the testAllOnes.vi and testGutterGame.vi share a piece of code that handles many rolls, which can be refactored into a standalone test VI. This section of code can be refactored into a standalone private method that can then be used within the testGutterGame.vi and testAllOnes.vi Now I should be able to run my test cases again and have them both pass. Third test - failThe first two tests were built to handle the simplest test cases in a game of bowling. The next test will address some of the complexity of how bowling games are scored by incorporating the ability to score a spare. This test will be named testOneSpare, and will involve rolling a score of 10 within in the first frame, rolling a score of three with the first roll of the second frame, and rolling 17 gutter balls to finish off the game. Based on how spares are scored in bowling I can assert that the score of the game as defined in this test case should be sixteen. I can run the test cases and see that this assertion is not met, and that the testOneSpare test case fails. Third test - passSo how do I make it pass? My first thought may be to modify the Roll method to remember the number of pins passed in from the previous roll, and if current pin count plus previous pin count is equal to ten then assume a spare and set a flag in class data that would add the next roll twice. Something doesn't smell right about this solution, putting a flag in class data never seems like a clean solution. That smell is the aroma of misplaced responsibilities. Right now I have two methods in the production code, the Roll method and the Score method. Which of those two methods actually calculates the score of the game? Based on the naming we would expect that the Score method would calculate the score, but in reality it is the Roll method performing the calculation. Let's see if by re-aligning the responsibilities of our code I can find a better way to handle spares. In TDD, when existing code needs to be refactored to satisfy a failing test it can be beneficial to comment out, or skip that test and refactor while ensuring that all previously passing tests still pass. third test - RefactorI need to refactor the Roll and Score methods of my BowlingGame class so that the Roll method keeps track of the pins knocked down per roll and so that the Score method calculates the score of the game at the current point in time. I will start by refactoring the class data for my BowlingGame class so that it has an array of integers that can store the number of pins knocked down per roll, and so that it has an integer that keeps track of the current roll number. I can now refactor the Roll method so that each time it is called it will increment the currentRoll integer, and will add the current pin count to the appropriate place in the roll array. Next I can refactor the Score method so that it sums the values of each index of the roll array and passes that sum out as the score. Let's see if the testGutterGame and testAllOnes test cases still pass. Third test - FailAfter enabling the testOneSpare test case I will run my tests through the UTF and I should see the testOneSpare test case fail. Third Test - PassNow I will need to write the simplest code possible to make this test case pass. Taking another look at the Score method my first inclination is to check and see if roll(i) + roll(i+1) = 10, if so then I have a spare. However, this logic is incorrect because spares can only occur in frame increments (if my second roll of frame one was a five and my first roll of frame two was a five I should not get credit for a spare!). So there is still a problem with the logic, time to refactor. Third test - refactorI can't refactor with a failing test so the first thing I need to do is to skip the testOneSpare test case again. With that test case being skipped over by the UTF I am again in a passing state, so let's refactor. I need to refactor the Score method so that it steps through the rolls one frame at a time, checking for spares one frame at a time, not one ball at a time. I now iterate through the rolls array by frame and sum each frames score to calculate the total score for the game. Next I need to run my test cases against the refactored code to make sure that they still pass. Third Test - FailNow I can unskip the testOneSpare test case and run the test cases through the UTF to make sure that testOneSpare fails. Third Test - PassHaving gone through the Red-Green-Blue cycle a few times now for this test, I am finally ready to write the code necessary to make the testOneSpare test case pass. Now that I am evaluating the score on a frame-by-frame basis I can update the Score method to check for spares by frame and implement the spare scoring rules. The code now checks to see if the score for the frame is ten, if it is then the first roll of the next frame is added to the score of ten for the frame with the spare. Using the UTF to run all three test cases now returns three passes. Third Test - RefactorThe testOneSpare case is passing! That is great news, but before moving on I need to take a moment and refactor the code. I will note that the refactoring that Robert Martin does in the kata is going to look different than the refactoring that I do in LabVIEW because his language is text base and our language is more advanced. Refactoring the Score method mainly involves making the code more readable. I will also revisit my test code to see if it needs any refactoring, remember that test code is just as important as production code and that they will both rot if neglected. Refactoring the testOneSpare.vi involves the creation of an explicit Roll Spare private test VI that aids in making the code more readable. Now that refactoring is complete, let's run the test cases one more time to make sure that they all still pass. Fourth Test - FailThe next most interesting test case will test one strike in the first frame, followed by a three and a four with each roll in the second frame, followed by eight frames of gutterballs. The test code for this test case will look similar to our test code for the testOneSpare test case. Using the UTF to execute the test cases should return a pass for all but the new testOneStrike test case. Fourth test - passIn order to get the testOneStrike test to pass I will need to add some logic to the Score method. I was able to do a lot of the heavy lifting in the Red-Green-Blue cycle of the testOneSpare test case, so making this test pass should be straight forward. The logic here is that if the first roll of a frame is equal to ten then it is a strike and each roll from the next frame should be added to the score of the strike frame. If the first roll of a frame is not a strike then the same logic we used for the testOneSpare case is used. I should now be able to run my test cases with the UTF and see all four pass. Fourth Test - RefactorIn the kata Robert Martin goes through and refactors both the Score method and the testOneStrike test code, creating a private method of the BowlingGame class that evaluates if a roll is a strike, and creating a Private Test method for the test code for rolling a strike. After refactoring the test cases should all still pass. Fifth Test - FailThe last test of the kata will test a perfect game, a game in which the bowler rolls all strikes. Now that the test code has been written I can run my test cases with the UTF and expect the testPerfectGame test case to fail. But wait a moment, all of the test cases passed, even the new testPerfectGame test case. It turns out that the scoring algorithm is now complete and that any type of game could be tested successfully. This is a surprisingly simple solution, especially when compared to the UML diagram that Robert Martin drew for us at the very start of the kata. SummaryIf you made it this far I would strongly encourage you to try coding up this kata on your own, I think you will find it beneficial. The main takeaways for this exercise should be that TDD is more than just writing unit tests first, TDD will actually guide your design (hopefully towards simplicity). We also got to see how the Fail-Pass-Refactor cycle works first hand, which should be a good introduction to TDD in LabVIEW. One last thing I will mention, and this is something that I really like about TDD, the Refactoring of both the production code and the test code as part of the Red-Green-Blue cycle greatly aids in writing clean code (readable, maintainable, scalable). Take a look at Russell Blake's post on this code kata using the JKI VI Tester. Be on the lookout for an upcoming blog post by Omar Mussa, which will walk through this same kata using Caraya from JKI. These posts will do a good job at illustrating the differences between the workflows of the different tools. Jon McBee is a Principal Software Engineer at Cambridge NanoTech and is a Certified LabVIEW Architect, Certified LabVIEW Embedded Developer, Certified TestStand Developer, an NI Certified Professional Instructor, a LabVIEW Champion, and a Certified ScrumMaster
3 Comments
Paul Ross
6/2/2016 09:56:50 am
Hey Jon,
Reply
Jon McBee
6/2/2016 10:22:30 am
Hey Paul,
Reply
6/2/2016 10:45:03 pm
Hello Jon and Paul, Leave a Reply. |
Tags
All
Archives
October 2019
LabVIEW Blogs |