The other day I was working on creating widgets to make it easier for me to configure the elements of a screen in a game I’m working on.
At one point I realized that my interface class could hold the common data between the different derived classes, such as the widget’s position. There’s no reason that each derived class needs to have code copied to manage it, so I moved it into the base class:
// IWidget.cpp IWidget::IWidget(const Point & position) : m_position(position) { } // SpriteWidget.h class SpriteWidget : public IWidget { public: SpriteWidget(SpriteImage * sprite, const Point& position); . . . // SpriteWidget.cpp SpriteWidget::SpriteWidget(SpriteImage * sprite, const Point & position) : IWidget(position), m_sprite(sprite), m_buttonControl(0) { }
It should have been a very basic refactoring exercise, and yet I found a number of unit tests were failing that made no sense to me. It was as if the member variable m_position wasn’t getting correctly initialized when I created my SpriteWidget.
By default, a Point object initializes to (0, 0, 0), but as you can see above, my SpriteWidget passes along the Point position to the IWidget’s constructor, which initializes it’s member variable. And yet my unit tests indicated that SpriteWidget was not initializing IWidget’s Point correctly and was instead getting m_position initialized to the default values.
Reading the code, everything looked right, but clearly something was wrong. Otherwise, the tests would be passing.
So I started printing out logs to let me know the state of the variable. SpriteWidget’s constructor got position just fine as an argument. And it was correct in IWidget’s constructor. And yet when I tried to print out m_position from within SpriteWidget’s constructor, it returned values for a default Point.
And then I found it.
// SpriteWidget.h . . . private: Point m_position;
I forgot to take the member variable out of SpriteWidget’s class definition, so it was shadowing IWidget’s otherwise inherited member variable, and I didn’t realize it.
But my unit tests were there to let me know that all was not well. Without those tests, I might have thought my code was fine. After all, the logic was correct, which I can verify after rereading it multiple times.
So I probably would have seen this unexpected behavior potentially weeks or months later, especially if my first production use of these widgets used the default position value of (0, 0, 0). It would have taken me forever to track it down, which would have been a disaster during a Ludum Dare timed compo.
Thanks for saving me time and preventing stress, unit tests!
3 replies on “Unit Tests Save The Day Again”
Awesome stuff. I’ve been bolting unit tests on my framework for a while now, but I’m curious what your tests would look like for this situation given it is more UI related. I guess it could just test with accessor?
Point testLocation(50, 50, 0);
SpriteWidget testWidget(testLocation);
assert(testWidget.GetPosition() == testLocation);
Yeah, that’s probably what it would look like if you’re testing that the initialization worked as you expected. Another way is that when you draw it does it create an object? If so, check that object.
For my code, a sprite creates a render command, and I can check that it’s position is what I expect, that it is the right kind of render command, and that it has all the data I expect for this sprite.
A lot of compilers would flag that as a warning, so you may want to look into setting your warning level higher.
I’m not sure if it would catch this particular issue, but you should also check out CppCheck, which is a free static code analyzer. PC Lint would definitely catch that issue, but it’s not free.