Wednesday, July 8, 2009

Real programmers write unit tests (part 2)

In this post I will discuss some of the techniques that I use to work with external libraries and legacy code.
At this point I'm still experimenting with this. However, all of the techniques that I will discuss here have been very useful in several cases. All of the techniques that I have used require to make some compromise
about the simplicity or sometimes what I would call the OO purity of the code. However, in all the situations where I have used those techniques the gains were worth the sacrifices.
But first we need to talk a little bit more about the setup for the tests.

Test setup


Since writing unit tests adds a good number of files to the source directories we decided to put our test code under a different base directory than the one used for the tested code. Because of Java's visibility rules we use the same package (namespace) for a test class than for the class that we are testing. Thus, our test code directory structure mirrors that of the tested code. We put the test code under Test and the tested code under Src. This makes it easier to process the files separately as part of our daily Ant build (run unit tests, metrics and other Ant tasks).

Unit test best practices


I will not cover all the unit tests best practices here. If you want more information about that just read JUnit best practices at http://www.javaworld.com/jw-12-2000/jw-1221-junit.html . Since several unit test tools (Nunit, DUnit) are ports of Junit those best practices apply on any platform or language with little modifications. C# programmers might want to read NUnit best practices at http://scottwhite.blogspot.com/2008/05/nunit-best-practices.html . Make sure you read the comments.

The recipes


I will use names inspired by the names used for refactorings in Marin Fowler's Refactoring for my recipes.

Introduce middle man


Normally if a class doesn't do much you want to remove it. For example, if a class just delegates calls to a member. The refactoring for this is called Remove middle man.
For unit testing involving legacy or third party libraries it can sometimes be useful to introduce such a class. I call this Introduce middle man.
For example, in my last project I decided to create an ErrorHandler class. I needed to be able to implement an error handling logic that was a little more elaborate than usual. One thing that this class had to do was to interact with an OPC UA Session object. Now building an OPC UA Session object is not easy. It requires a bunch of other code and because the Session class does not implement any simple abstract interface that I could use I was in a difficult position to write unit tests for my ErrorHandler class. Fortunately, my code only called one Session class method (Reconnect) so in order to write good unit tests I decided to introduce a middle man. I chose to create an Interface ISessionConnector with one method: Reconnect. Then I created a class than just delegated the call in the Src branch and created an implementation in the Test code branch that allowed me to simulate different scenarios (reconnection failure or success). I was able to get 100% test coverage of my new class. Another advantage of this approach is that I don't actually have to get an OPC UA server running in order to run this test. Good judgment is essential here. You should watch for middle man that gets too complicated. You might end up not testing the right code. If the middle man remains simple this works well. In my case, middle mans have often allowed me to test several classes that would have been difficult to test otherwise in legacy code or code that used third party libraries.

Parting comments


Some people might wonder why I don't use mock libraries like Jmock or others. The reason is simple: none of the mock libraries work the way I want. I plan to use mock libraries in the future but probably not exclusively.
Next time I will talked about another unit testing trick to handle legacy code: Extract static method.

No comments:

Post a Comment