LabVIEW Craft
  • Blog
  • About
  • Contact
  • Tools
  • Resources

Bowling kata - Unit test framework

6/2/2016

3 Comments

 
Jon McBee
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.
Picture
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.

Begin

We begin the kata by creating a new LabVIEW project named Bowling Game.
Picture
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.
Picture
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.
Picture

First test - Fail

The 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.
Picture
By right-clicking on the target we can select New>>Unit Test.
Picture
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.
Picture
Picture
Picture
This first failure stems from the fact that the UTF needs a VI to test, so lets fix it.

First Test - Pass

In 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.
Picture
With the UTF we will need to create a User-Defined Test Template.vit in order to test our BowlingGame class.
Picture
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.
Picture
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.
Picture
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.
Picture
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.
Picture
I can now execute the UTF and run my testGutterGame test class and it should pass.
Picture
Picture

First Test - Refactor

I don't really have anything to refactor at this point.

First Test - Fail

The 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.
Picture
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.
Picture
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.
Picture
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.
Picture
Because the Score method is incomplete I have hard-coded the output to return -1.
Picture
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.
Picture
Picture
Now I need to make this test pass.

First Test - Pass

Because 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.
Picture
Now I can run the test case using the UTF and get my passing result.
Picture

First Test - Refactor

I 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.
Picture
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%.
Picture
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 - fail

Now 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.
Picture
I can create a User-Defined Test from the testGutterGame.vi and run my tests through the UTF.
Picture
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 - Pass

In 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.
Picture
The first thing I will do is add an integer named Score to my BowlingGame class data.
Picture
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.
Picture

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.
Picture
This section of code can be refactored into a standalone private method that can then be used within the testGutterGame.vi and testAllOnes.vi
Picture
Now I should be able to run my test cases again and have them both pass.
Picture

Third test - fail

The 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.
Picture
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.
Picture

Third test - pass

So 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.
Picture
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.
Picture
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.
Picture

third test - Refactor

I 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.
Picture
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.
Picture
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.
Picture
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.
Picture

Third test - Fail

After enabling the testOneSpare test case I will run my tests through the UTF and I should see the testOneSpare test case fail.
Picture

Third Test - Pass

Now 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 - refactor

I can't refactor with a failing test so the first thing I need to do is to skip the testOneSpare test case again.
Picture
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.
Picture
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.
Picture

Third Test - Fail

Now I can unskip the testOneSpare test case and run the test cases through the UTF to make sure that testOneSpare fails.
Picture

Third Test - Pass

Having 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.
Picture
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.
Picture

Third Test - Refactor

The 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.
Picture
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.
Picture
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.
Picture

Fourth Test - Fail

The 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.
Picture
Using the UTF to execute the test cases should return a pass for all but the new testOneStrike test case.
Picture

Fourth test - pass

In 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.  
Picture
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.
Picture

Fourth Test - Refactor

In 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.
Picture
Picture
After refactoring the test cases should all still pass.
Picture

Fifth Test - Fail

The last test of the kata will test a perfect game, a game in which the bowler rolls all strikes.
Picture
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.
Picture
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.
Picture

Summary

If 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
bowling_game_kata.zip
File Size: 2240 kb
File Type: zip
Download File

3 Comments
Paul Ross
6/2/2016 09:56:50 am

Hey Jon,
Thanks for writing this up. In writing these tests, you ended up with 8 test .vis in addition to the 5 .lvtest files. One can see how you would end up with a significant amount of test code on a large project. Any thoughts on managing that additional code? Do you maintain your test code in a completely separate file hierarchy from the source code? Similarly, do you keep a separate project for the tests, or do as you did in this example and develop both test code and source in the same project?
Thanks,
Paul

Reply
Jon McBee
6/2/2016 10:22:30 am

Hey Paul,

Thanks for reading, I wasn't sure anyone would read this one as it has a bit of length to it. I attached the source code used for all the screenshots just above so you can download and play with it, it is in LV2015.

My inclination is to say that test code (and test case files) should exist with the code they are testing, which would mean that they should all be in the same project. If you look in the zip directory you will see how I organized the files on disk, I would mirror this organization in source code control.

I agree that the file count will grow with large projects, I recently put a question up on linked in asking people if they develop with multiple projects and if so how it impacts their unit testing patterns. I posted this because I have the same questions that you have, and I am not aware of any best practices that exist for it. I am hoping that these posts will drive that conversation forward (or at least someone will point me in the direction of existing best practices).

Linked in discussion on multiple projects is here: https://www.linkedin.com/groups/2085938/2085938-6141774340527575041

-Jon

Reply
Russell Blake link
6/2/2016 10:45:03 pm

Hello Jon and Paul,

I was also thinking about this same topic today as well. One constraint in using LV classes with multiple projects is that if you have both projects open at the same time LabVIEW locks the LV Class library and does not let you edit it. This could be a real pain if you are trying to do TDD with your source code being in a LV class in a separate project from your unit test code. You would have to close your project with the unit test code in it to make any changes to the LV class source code. This could really become time consuming and slow down the development process. I think if you are using LV classes and unit testing I would agree with Jon that you would want to keep it all in the same project.




Leave a Reply.

    RSS Feed

    Tags

    All
    Abstract Messaging
    Actor Framework
    Agile
    AI
    Asynchronous Programming
    Best Practices
    C#
    Complexity
    Continuations
    Control Values By Index
    Craftsmanship
    Dependency Inversion
    Dependency Viewer
    Futures/Promises
    Genetic Algorithm
    Liskov Substitution
    Malleable VIs
    Mediator Pattern
    .NET
    Object Oriented
    Object-Oriented
    Packed Project Library
    Parallelism
    Polymorphism
    Pub/Sub
    RawCap
    Root Loop
    Scrum
    Task Parallel Library
    TCP/IP
    TDD
    Test Engineering
    UML
    Unit Test
    VI Scripting
    VI Server
    WireShark

    Archives

    October 2019
    April 2019
    July 2018
    October 2017
    March 2017
    February 2017
    January 2017
    December 2016
    July 2016
    June 2016
    May 2016
    April 2016
    March 2016
    February 2016
    January 2016
    December 2015
    November 2015
    October 2015
    August 2015
    February 2015
    January 2015

    LabVIEW Blogs

    Eyes on VIs
    LabVIEW Hacker
    VI Shots
    LabVIEW Artisan
    Software Engineering for LabVIEW
    ​Wiresmith Techology
Picture
Powered by the Panda Ant

  • Blog
  • About
  • Contact
  • Tools
  • Resources