Skip to main content

Unit testing introduction

I'm a fan of unit tests since I started writing them. So far, they also turned out to be very valuable, even if added late to a project, so let's take a look at this whole unit test thing -- what it is, why you should do it, how to do it, and some practical notes.

Who? What? Why?

A unit test is a test which covers a small unit of functionality. Ideally, you would have one unit test for each "leaf" function, or at least for functions which operate on isolated state. This has a nice side effect that unit testing helps to write side effect free (pun intended ;) ) code, as you want to test each function individually.

For object-oriented code, you probably want to test single objects or at most the interaction of two objects in a test. There are lots of nice examples, to take one from my work: Recently, I implemented a tiny graph class, and wrote unit tests to add a node, connect two nodes, check that the edges exist and similar low-level operations on the graph.

But why unit test in the first place? Sure, it increases the code coverage counter, but what else? I see mainly two points for unit testing: You get more isolated objects, and you can easily refactor or optimise code later. The latter is actually my killer argument, because it's highly like that you will have to either add functionality or improve performance. In both cases, you want to make sure you have a "good" set of functions on which you can rely on.

One side effect of unit tests is that you usually get better debugging capabilities as well, as instead of debugging the whole app, you can debug individual unit tests. Plus, you can be sure that your stuff actually works at all -- if it passes the unit tests, it's at least not completely broken.

So what makes up a good unit test:

  • Fast: You should be able to run thousands of them per second. Loops, disk I/O, GUI creation is not unit testing! For I/O, consider to convert the binary data into a static array and compile it into the tests.
  • Single point of failure: If a test fails, you should have to look at only one piece of code. That is, you should avoid testing several different things in a single test -- better write individual ones -- and you should also try to check only one code path per test. If you have a function which returns an error if a parameter is wrong, and a valid result otherwise, write two tests!
  • Deterministic: Don't mess around with random numbers, current time, date, CPU count or other stuff which might change. That means also that checking whether your CPUID instruction works is something that should be moved into a functional test.

When/How to start

There are two main approaches to unit testing, one is test-driven development, and the other one is -- everything else ;) With test driven development, you write a test first, then you write the code, and continue like this. It gives you very high coverage and nice interfaces, as you write the usage first. With TDD, the best way is to write tests right from the start of a project, or if you jump into one which does not have unit tests, then you write those for new code.

Adding tests to an existing project which didn't have unit tests in the first place is an interesting problem anyway. As a rule of the thumb, I tend to add unit tests before refactoring and optimisation. Especially for the latter, having a large test set is important, as you might easily wind up to break your function while throwing around with all these movmskps!

What I found to work in practice very well is to write the unit tests directly after designing the interface, as it gives you one additional information over writing the test along with the interface: If your interface turns out to be difficult to test, you immediately can check under which assumptions it has been created in the first place. If you start with the test right away, you might miss something, so in my opinion, it pays off to think twice about each interface.

Practical notes

If you're eager to add unit tests to your project, then read on for some practical advice:

  • For C#, NUnit and MSTest are very comprehensive test suites. They cover all you need, provide nice graphical tools and are very similar. Unfortunately, the order of parameters is slightly different, so switching between them might require some work.
  • For Java, I only used JUnit, and I never had something to complain.
  • C++: There are two unit test frameworks I can recommend, having excessive experience with them ;) (I'd guess 10x more than with Java and C# combined). First, there's Boost.Test, which is the canonical choice if you have to get running with unit tests quickly. In the latest version, it's easy to use, has minimal setup and well -- it's Boost, so it's tested on various platforms and has lots of docs. The alternative is UnitTest++, which is a tiny library. The nice thing about UnitTest++ is that it is so tiny, that it's worth to embed it into larger projects to be independent of Boost. Usually, if I know that I'll be writing lots of tests, I tend to prefer UnitTest++, simply as I can be pretty sure that in case I need to support a new platform, porting UnitTest++ will be easy (it has been ported to the XBox 360 for instance).

Of course, there are probably lots and lots of things I missed here, but I hope I could show why unit testing is actually something worth caring about. Especially when it comes to later maintenance of a project, having unit tests in place is a huge time saver. For me, these were the moments in which I was always happy to safely shuffle stuff around without randomly breaking things and noticing it much too late.