Practical Testing: 2 - The first test

I’m writing some blog entries that show how to write tests for non trivial pieces of code. This is part 2.

In part 1 I introduced the code that we’re going to write a test for. Now we’ll move towards the all important first test. The first test is, in my opinion, vitally important; the first test proves that you can build a test harness that can create the object. This sounds easy, but try picking a class from your current project at random and writing a test that constructs an instance of that class. The test should link in as little additional code as possible and should result in an instance that is fully usable; so if you use some freaky two phase construction method, like having to call a function to Initialise() the object after creating it then you need to do that as well…

Writing the first test exposes all of the explicit and implicit coupling that tethers your object to the rest of your code base. If you’re trying to test existing code that was never designed with testing in mind then it’s quite often the case that you need to link in some global objects, do some seemingly unrelated initialisation, or link with every library in your project… When you have the first test running you know that you CAN test; everything from there on is just typing ;)

So, we’ll add a test harness for the Win32Tools library. This is a console mode exe project that contains the test code and a simple driver that runs the tests. I don’t use C++Unit; convince me why I need it… I expect that NUnit and JUnit are more useful as they can use reflection to create the scaffolding. It’s my opinion that you should put the least things between you and getting to your first test. If you feel the need to refactor it all later to use a wiz-bang framework then fine, but get started first…

Updated 28th April 2023

The test project is simple, it includes Test.cpp, the test driver, and CallbackTimerTest.cpp and .h, the actual unit test. The driver calls TestAll() on the CCallbackTimerTest object and catches a range of exceptions. If the test doesn’t throw an exception then all is well and we’ve passed. It’s simple and I’ve been meaning to do it all in a cleverer way, but I’ve just not found the need. For me, if I have 10 tests then they should all pass. If 1 fails then the test harness is broken. I don’t need to see that 8 out of 10 pass, if the first one fails, that’s it, that’s where I need to work next…

The test harness exe links with the library under test. It also links with any libraries that the library under test depends on, like C++Tools. It also links with a library of test harness helper code, TestTools.

Getting to the first test means we need to pull in a little more of the real implementation of Win32Tools. We need SEHException code so that the test harness can report on any structured exceptions that the tests throw.

Looking at the test for CCallbackTimer you’ll see that it’s very simple. We log that the test has started, we create an instance of the object and we log that the test has finished. Hardly rocket science and probably something that would earn the scorn of those who despise unit tests but: We now have a framework for writing more tests. We could add a unit test for any of the other classes in Win32Tools with ease. We can add a new test for CCallbackTimer just by adding a new function to the unit test… We’ve proved that we can create the class in relative isolation. We’ve exposed any hoops we might need to jump through, or classes we might need to create before we can create our object under test.

The first test is important, sometimes I stop here. Often it’s not worthwhile to add more tests right away, but it is worthwhile to have a test that proves that your object isn’t becoming implicitly or explicitly coupled to other code. If you suddenly find a bug in a class that has a test harness you’re more likely to write a test to help you fix the problem, if you have to create a new project, build a test harness for the object and then work out what else you need to link to get the exe to run then you’re more likely to just tinker with the problem and try and debug it in place within the application. In choosing where to spend your test energy you get a lot of bang for the buck by writing the first test for an object…

Code is here. Win32Tools is the workspace that you want and Win32ToolsTest is the project that you should set as active.