Developer Drain Brain

January 4, 2010

Unit Testing Primer

Filed under: Development — Tags: , , , , — rcomian @ 11:24 am

At the TechEd conference I was able to listen to Roy Osherove give his talk on unit testing. Osherove is, as far as I can tell, one of the leading figures in unit testing and test driven development and his talks give the impression of someone who’s come to a very deep understanding of the subject, born through a great deal of experience and insight.

I’m going to try to cover the main points that Osherove gave in his talk, and the video will be available to those who wish to see it. In this post I’ll lay out the primers that are needed to understand how to write good unit tests. I don’t count myself as a unit testing guru, but I have made enough mistakes in this area to feel that I get where Osherove is coming from.

The first thing to understand is what a unit test is. A unit test does not rely on any environmental factor of any kind. This means no filesystem, database, network, specific user accounts or other tests. Anything that is relied on by the test is explicitly setup within the test. This also includes any functions that don’t return the same values each time they’re called – think DateTime.Now or Rand(). Any environmental dependance such as this means that the test must be classified as an integration test. Integration tests are a different kind of beast, and although still vital to the health of your software, are not the unit tests that we’re talking about here. It turns out that I’ve only ever written very few unit tests, the vast majority have been inadvertant integration tests.

The second thing to understand are the three pillars of unit testing: “Readability”, “Maintainability”, “Trustworthiness”. Each pillar holds up the other two pillars, you cannot have a maintainable test that isn’t readable. You cannot understand a test, you’re not likely to trust it. So if you find yourself criticizing a test based on any of these three pillars, then it’s likely that the test is not a good test and isn’t helping your software.

This leads on to the third fundamental point. A test suite filled with tests that aren’t good will not help us develop good software. In fact a bad test suite can be detrimental by forcing us to pay ever increasing testing taxes without them paying any dividends.

It turns out that there is a very clear pattern to good tests. They’re not really like normal code, so much, more like recipes. An template might be:

void <method under test>_<condition of test>_<result of action>()
{
    <initialise test items and condition>

    <perform tested actions>

    <assert on result>
}

In addition to this template, there are a few other fairly unexpected guidelines:

  1. Do not use conditional logic in the test (no IF’s or SWITCHes). Each branch of the logic is a separate test condition – split out to separate tests.
  2. Don’t assert in loops. Each loop is a separate test condition, split them out or recognise that they’re duplicates and remove them.
  3. Assert only 1 thing per test. Multiple asserts are generally testing for different results or conditions, so they’re separate tests.
  4. Give context-relevant names to the values being used. For example, calling doSplit(INVALID_LIST, UNUSED_FLAG) says more than doSplit(“here there”, false)
  5. Separate out each step. Whilst you might find that you can initialise, act and assert all on one line, it doesn’t mean that it’s readable.
  6. Do use standard refactoring techniques to re-use common code parts, but all the above advice still holds for these refactored methods.

He also gives some suggestions for the lifecycle of tests:

  1. Add new tests when new methods, conditions or results become relevant.
  2. Remove tests when they become irrelevant.
  3. Only modify tests if the API changes or they need refactoring.

This is a very quick distillation of the advice given. Osherove himself provides a lot more detail and justification for each point here.

See his site and other links at http://osherove.com/

Blog at WordPress.com.