Thursday, May 04, 2006

Unit testing granularity

I read this post from Jason, who has recently taken up his game development project and related blogging. Keep up the good work dude! His post touched on granularity in unit testing, and this inspired me to blog.

When I started unit testing my testing was very structured so I had a test class per real class and a test method per method and per property. That worked well for a while, but for a large project I ran into the problem that my test methods became quite large because I tested the effect of the method in several different scenarios. My case was to try to optimize the test to test everything the method did in one place. I also methodically tested assumptions before doing the actual test - often testing that the same call did not return null before working with the actual result several times in a method. The result was often very long test methods for a single method - but at least I knew where that perticular test was and could track where the event went wrong.

After attending a course where we touched on the subject, I got a few points hammered down and I changed my strategy a bit and begun testing with a higher granularity. Now I have several test methods per method where every test test exactly one aspect of the method. I make sure that no tests are repeated, so if I have a test that test that under the conditions set up the method does not return null, I exepect the result not to be null in other tests. Furthermore I set up several distinct test classes for a class if there are different preconditions for the class. This is usually classes that hold some stae but are note yet ripe for being refactored into different state classes. The test class set up the right preconditions and then test the class under those conditions.

The end result is that I now have a significantly higher amount of test methods, and some of them correspond directly to methods, others correspond to different aspects of the functionality of the function. All test methods are also very short, having only one or two assert statements each. The effect is that though you have to look for the right method that fails, and you can have false positives in your failures, the code is much easier to read and understand. Furthermore when you change functionality for a method, I have to change only one or two test methods in order for it to work. Before I had to change several places in a single test method that could be hard to find, and because I was "methodical" enough to test my preconditions, a change could very well mean changes to several other test methods as well.

So my take on granularity is to keep it high. Make small methods that test one aspect of one method, and one thing only. Name the method so you know what it tests. When testing complex scenarios, set things up as preconditions in a sepereate class, write a single test that sees that the preconditions are met, and then assume that they are met in the oher test methods.

No comments: