Logic Puzzle Solution Part 2: Testing the neighborhood Class Module
This post is a followup to the Five-House Logic Problem. I described how I worked out the solution by hand in an earlier post (warning: contains spoilers). This post is Part 2 of my Microsoft Access solution (see Part 1 here).
The oNeighborhood Class
I presented the oNeighborhood class module in yesterday's article:
As I wrote, the crux of the class module was the SatisfiesRiddle function. The function returns True if the neighborhood configuration meets all the criteria of the riddle and False otherwise.
This is the sort of complex (but deterministic) function that benefits most from a test-driven development (TDD) approach.
Simplified Testing
There are many benefits to a rigid TDD approach to programming.
Unfortunately, the lack of a good testing framework in VBA makes this approach more difficult than in other languages. Let's not let the perfect be the enemy of the good, though.
Even in languages with strong testing frameworks, it can be easy to get overwhelmed when trying to set it all up.
In reality, you don't need a framework to get the main benefit of testing: proving that your logic works as expected.
Maximizing Testing ROI
With many unit testing frameworks in other languages, you end up spending a lot of time writing tests for simple logic in the name of boosting your "test coverage."
Now, there's nothing inherently wrong with that approach, but it doesn't give you the best bang for your buck. You can write a bunch of tests and get to 90% test coverage by prioritizing easy-to-write tests that help you eliminate runtime errors. But that would be less effective than writing tests to cover the 5% of your code where your logic errors are most likely to live.
Both sets of tests will help you move up the bug type list, but reducing logic errors is way more valuable than reducing runtime errors.
Three Simple Tests
While I was writing the code for the oNeighborhood class, I wrote three simple tests to make sure everything worked the way I was expecting.
TestUnsetNeighborhood
In the first test, I wanted to make sure that my unset enum practice was working as intended.
The idea behind including an "Unset" enum item is that you avoid having unintentional default enum values. To test this, we create a new instance of our oNeighborhood class. Without setting any properties on the class instance, we first check to see if it satisfies the riddle, and then we output a description of the neighborhood configuration to the Immediate window.
This routine shows that no enums have been unintentionally set.
Sub TestUnsetNeighborhood()
With New oNeighborhood
Debug.Print "Satisfies riddle? " & .SatisfiesRiddle
Debug.Print .NeighborhoodDescription
End With
End Sub
TestSampleNeighborhood
In the second test, I wanted to make sure that setting values for house color, drink, pet type, etc. would actually result in them being set within the class module.
I never intended for this configuration to satisfy the riddle, so it makes no difference what values get set where. We just want them all filled in. The simplest way to accomplish that was with a for loop.
Sub TestSampleNeighborhood()
With New oNeighborhood
Dim i As Byte
For i = 1 To 5
.SetColor i, CLng(i)
.SetDrink i, CLng(i)
.SetFlower i, CLng(i)
.SetOwner i, CLng(i)
.SetPet i, CLng(i)
Next i
Debug.Print "Satisfies riddle? " & .SatisfiesRiddle
Debug.Print .NeighborhoodDescription
End With
End Sub
Here's what it looks like in action:
As expected, this neighborhood configuration does not satisfy the riddle, but it does prove that our five .SetXxx subroutines are working as intended.
TestSatisfiedCondition
OK, I admit that I cheated on this one. I had already worked out the solution to the logic puzzle by hand. This meant that I knew what configuration should satisfy the riddle. In fact, having this insider knowledge helped me make several changes to get the SatisfiesRiddle function working correctly.
In any case, the main purpose of this test routine is to make sure that SatisfiesRiddle returns True when presented with a neighborhood configuration that satisfies the logic puzzle's 15 rules.
Sub TestSatisfiedCondition()
With New oNeighborhood
.SetColor 1, hc_Yellow
.SetOwner 1, on_Norwegian
.SetPet 1, p_Fox
.SetDrink 1, d_Water
.SetFlower 1, f_Roses
.SetColor 2, hc_Blue
.SetOwner 2, on_Ukrainian
.SetPet 2, p_Horse
.SetDrink 2, d_Tea
.SetFlower 2, f_Marigolds
.SetColor 3, hc_Red
.SetOwner 3, on_English
.SetPet 3, p_Snails
.SetDrink 3, d_Milk
.SetFlower 3, f_Geraniums
.SetColor 4, hc_Ivory
.SetOwner 4, on_Spanish
.SetPet 4, p_Dog
.SetDrink 4, d_OJ
.SetFlower 4, f_Lilies
.SetColor 5, hc_Green
.SetOwner 5, on_Japanese
.SetPet 5, p_Zebra
.SetDrink 5, d_Coffee
.SetFlower 5, f_Gardenias
Debug.Print "Satisfies riddle? " & .SatisfiesRiddle
Debug.Print .NeighborhoodDescription
End With
End Sub
The Tests Worked
When I say the tests worked, what I mean is that they helped me modify my code to make it correct.
I don't show the full back-and-forth that I went through to get the class module to its final working state, but you will have to trust me that having these tests available made the job easier than if I had to rely on my brain to do all the work.