As a follow up to the post, Bowling Kata - Unit Test Framework written by Jon McBee, I will be following the same Bowling Game Kata by Robert C. Martin using the JKI VI Tester. Because the original kata was written in Java, you will notice that Jon and I took different approaches when translating it to LabVIEW. You will also see differences based on the unit testing tools we each used. Hopefully you will find these differences interesting and informative. To give another valuable perspective, Omar Mussa will follow up with a post that goes through the kata using Caraya.
First I create a project named BowlingGame.
Next I create a unit test named BowlingGameTest.
I execute the unit test:
I verify that no tests were run:
FIRST TEST - FAIL TEMPlATE
I am ready to create the first test, testGutterGame. I will right-click on the testExample.vit and select New from Template from the menu.
Then I will go to File>>Save and name the test as testGutterGame.vi. VI Tester requires that all of the tests have the word "test" in the name. This actually works out great for the kata because all of the kata tests have "test" in the name as well.
Since I created the testGutterGame from a template there is already code in the block diagram. This code will pass if the actual result string from the temp_VI_UnderTest.vi is equal to the expected result string. The actual result string has a constant of "cat" wired to it. The result string has a constant of "dog" wired to it. Since dog is not equal to cat, the test will fail in its current state. When I run the test I see that it does actually fail.
FIRST TEST - PASS GAME CLASS
Now I want to make the test pass. In test-driven development (TDD) the goal is to write the test code before any production code is written. In the kata, the first test constructs a Game class before the Game class exists. The test fails because the Game class does not actually exist.
In order to make the test pass all Robert Martin does is just create the Game class without changing the test code itself. He follows a similar process with the creation of the roll and score methods (see slides 12-17).
In LabVIEW in order to write a test that references the Game class, the roll method, and the score method before they actually exist, I would have to dynamically launch the class and methods from disk using the Get LV Class Default Value.vi and VI Server. This would require a lot of extra code that I would ultimately be deleting and replacing when I refactor the code. I would never recommend going down this path since the goal of this step of the kata is to make the test pass with the least amount of code as possible.
Instead, I will create the Game class, the roll method, and the score method before I actually reference them in the testGutterGame.vi. First I will create the Game class.
I will then drop the Game class constant in the block diagram of the testGutterGame.vi and change the expected result string to cat so the test passes.
FIRST TEST - FaIL
The point of this first test is to verify that if every throw goes into the gutter, the score should be zero. To accomplish this task as quickly as possible, I need to create the shell VIs for the roll and score method. The score method will return a score of -1.
I will write the gutter game test code so that the roll method is called 20 times (once for each roll) with no pins knocked down on each roll. Then I will add the score method to determine the score. The passIfEqual.vi passes the test if the score method returns a score of zero.
The test fails because the score method returns a score of -1.
FIRST TEST - PASS
In order to get this test to pass, I will change the score output in the score method to zero.
Now the first test passes.
FIRST TEST - REFACTOR
Now that the testGutterGame is passing, I can take this opportunity to refactor the production and test code. Since there isn't much production code yet, I will just remove the unnecessary case structure inside of the roll and score methods.
Along with the production code, I also want to make sure I am looking at how I can improve the test code. I will remove the temp_VI_ VI Tester example template and change the icon to say Gutter Game instead of Example.
I will run the test again to verify that it passes.
SECOND TEST - FAIL
The first test was a very simple test. For the second test each throw will only hit one pin down. I need to write a test that will verify that after 20 throws the score will be 20.
The gutter game test still passes, but the new test fails. Now I will get the second test to pass by adding code to the roll and score methods.
SECOND TEST - PASS
The first thing I need to do is add the score variable to the private data of the Game class.
I will update the roll method so that every time it is called it adds the number of pins for that roll to the current score stored in the private data of the class.
I will update the score method so that it just returns the score stored in the private data of the class.
Now both tests pass: testGutterGame and testAllOnes.
SECOND TEST - REFACTOR
Now that both tests are passing this is a great opportunity to refactor the code. I still do not have a lot of production code to change at this time, but I can make some improvements to the test code. Both test include a Game class constant and a for loop to sum the rolls. The VI Tester test case already has a method called setUp that I will use to resolve the Game class constant duplication.
I will add Game class to the private data of the BowlingGameTest class.
Now I will modify the setUp method to store the Game constant in the private data of the test case. This is really powerful because in the future I could add a new Game method to the setUp method to initialize the private data in the Game class for all tests instead of duplicating that initialization code in each test.
I will update our two tests to use the Game class from the private data of the bowling game test class. I will then run the tests to make sure they both still pass.
Next I will create a private test method called rollMany.vi. The rollMany method will call the roll.vi a variable number of times each with a variable number of pins knocked down.
Then I will replace the existing code with the rollMany.vi in the testGutterGame and testAllOnes and run the tests again to make sure the two tests still both pass.
ThiRD TEST - FAIL
So far I have not tested a spare or a strike. For the third test I will create a test with one spare in the first frame of the game.
This test failed because the roll method is not adding the bonus value of three to account for the spare.
THIRD TEST - sKIP
In order to make the test pass, I need to change the code. I could use a flag to remember the previous roll, but that does feel like the best solution. Also if we look at the current code closely we see that the roll method calculates the score, but the score method does not. Obviously the responsibilities are incorrect and these methods need to be redesigned. I need to make some major changes to the code. I will skip this test for now while I rewrite the code. Skipping an individual failing test allows me to determine quickly if a new code changes fails a previously passing test unintentionally.
THIRD TEST - REFACTOR
I want to change the role of the roll and score methods. The roll method should keep track of the rolls and the score should calculate the current score. I will create an array of integers to store each roll and an integer to store the current roll number. I will add the rolls array and currentRoll to the private data of the Game class. In order to streamline the process, I will not follow the kata exactly. I will delete the score variable in the private data of the class while adding the new elements to the private data. This causes the roll and score method VIs to break, but I am fine with that because I are going to change those VIs next.
I will rewrite the roll method first. The roll method will now increment the currentRoll variable and add a new element to the rolls array equal to the number of pins for that roll.
I will update the score method. The score method will calculate the sum of the rolls array. After making this change, I will run the test again to make sure the first two tests still pass.
THIRD TEST - FAIL AFTER REFACTOR
I will enable the third test again, but it still fails.
THIRD TEST - SKIP AFTER REFACTOR
In order to get the test to pass as quickly as possible, I could write code to loop through each element of the rolls array. If the sum of the current roll plus the next roll are equal to ten then I would indicate that a spare has occurred. But this will not work because it does not account for the concept of a frame and will return an incorrect score potentially. So what I need to do is skip the third test again and refactor.
THIRD TEST - REFACTOR FRAME
I will now rewrite the score method so that it has the concept of frames by looping through the entire rolls array by two rolls, because there are typically two rolls in a frame. For each iteration of the loop the code takes the sum of the current roll, next roll, and the accumulated score to calculate the score for the current frame. The first two tests still pass and the third test is skipped.
THIRD TEST - FAIL AFTER REFACTOR FRAME
If I again enable the third test I will see it still fails because I have not added any additional code yet to make it pass.
THIRD TEST - PASS
In order to make the third test pass, I will add a case structure to add the next roll to current frame score if the sum of the two rolls in the current frame equals ten.
If I run all of the tests again, all of the tests pass.
THIRD TEST - REFACTOR AFTER PASS
I will rename the internal variable index to frameIndex so it is more descriptive. I will also create a private method in the Game class called isSpare. isSpare determines if a spare occurred in the frame .
Lastly, I will create a new private method called rollSpare in the BowlingGameTest class to abstract the rollSpare logic inside the testOneSpare test.
FOURTH TEST - FAIL
I will create a test to simulate a strike in the first frame. The fourth test fails because I have not put any logic in the production code yet to handle a strike.
FOURTH TEST - PASS
To make this test pass I need to add the next two rolls to the current roll if the current roll is equal to ten.
When I run the test the testOneStrike passes.
FOURTH TEST - REFACTOR
Now that the fourth test is passing it is time to refactor. For this refactor session I will focus on creating subVIs or private methods for the production code and test code. First I will focus on the production code and specifically the score method. Since I add the two throws in a frame in two location in the code (isSpare.vi & when there is no spare), the sum of the two throws in the frame is a great candidate for a subVI. So I will create a method called sumofBallsinFrame.vi. I will add sumofBallsinFrame to the score method and the isSpare method.
I would like to document better the code that returns the spare bonus score. I will create a new method called spareBonus.vi and add it to the score method.
I would like to document better the code that returns the strike bonus score. I will create a new method called strikeBonus.vi and add it to the score method.
I used a comment to indicate that a strike has occurred. Instead I will make a new method called isStrike.vi and add it to the score method.
I have made all of the changes I want to make to the production code. Now I want to look at where I can improve the test code. In testOneStrike I am using a comment to indicate that a strike occurred. Instead I will create a method called, rollStrike.vi and add it to the testOneStrike test.
I want to create one last test to verify that a perfect game (12 strikes in a row) returns a score of 300. When I run the tests, I see that all of the tests pass without any code changes.
In conclusion, I created five tests that progress from a simple gutter game all the way to a perfect game. For each test, I wrote the minimum amount of code to make the test pass. Sometimes I decided to skip a test so I could make changes to the production code and easily see if any of my changes caused the other tests to fail. Once the test passed, I improved the code without changing the functionality. The tests allowed me to verify that my code changes did not undo previous progress. The kata ended up with a much simpler solution than the original design by Robert Martin at the beginning of the kata (his original design had four classes instead of the one class, Game, he actually implemented). If you want to learn the test-driven development process and become familiar with unit testing tools available for LabVIEW, I encourage you to code the kata on yourself.
Russell Blake is a Senior Program Manager at G Systems L.P. He is a Certified LabVIEW Architect and National Instruments Certified Professional Instructor with over 15 years of industry experience. He holds a Bachelor of Science in Electrical Engineering from The University of Texas at Austin and a Master in Science in Engineering Management from Southern Methodist University.
© Copyright 2016 G Systems L.P.