Wednesday, October 24, 2007

Unit Testing Guidelines

Ravi had blogged about the difficulties of getting people to write unit tests. I could relate to him since I have come across this problem quite often.

IMHO a developer gets turned off from writing test cases because

  1. Most developers dont have a clue on how to write proper unit tests. Most end up thinking integration test cases instead of unit testing.
  2. Proper unit testing (not integration testing) is hard work and needs proper design
  3. Estimating for unit test cases are not done or is under-estimated since we tend to have close to 2X lines of test code for a code of size X lines
Many times I have had to make developers understand what a unit test is and how to approach it. And this is the general guidelines i normally give them.

A unit test in my definition should
  1. Should test only one class - Even if a method in the class under test, calls methods on other dependent classes, this test should be responsible only for verifying that this method works fine provided the dependent classes return correct values.
  2. Continuing from 1, the dependent classes should have its own tests to verify all possible code flows. Doing this from a higher layer increases the # of test cases you have to write.
  3. The unit tests for the higher layers (above DAO layer) should use Mocks (and ofcouse Dependency Injection). Use either jMock or EasyMock to mock out calls to other layers. If you are unit testing without Mocks it means you are doing integration testing between two classes since you verify functionality of both.
  4. Test boundary conditions like what happens if you pass in a null object, what happens if your dependent class throws an exception etc.
  5. Test that the class throws all exceptions declared in @throws (and any runtime exceptions) exactly under the conditions documented
  6. Test DAO's even if you are using ORM tools, by using an in-memory DB like Derby or HSQL

In addition to above a code coverage tool like EMMA or Clover is a must have tool to capture coverage and draw attention to lesser tested parts of the application. Configure this to generate a daily/weekly report or better still hook it upto your cruise control. In most cases the developer themselves take it up as a challenge to get the code coverage up.

Subscribe to comments for this post

2 comments:

Hendry Luk said...

Regarding testing all possible flows, IMHO Java language design has biggest flaw in this. Especially checked-exception *feature*.

E.g. you have interface method that throws ExceptionA, ExceptionB, ExceptionC.
First... if your implementation contains block that throws other type of exception, you will need to add catch block that does nothing but to wrap it with either ExceptionA, B, or C. I used to write all unit-test to cover these block, but it gets old pretty quickly and feels pretty lame.
Secondly... suppose you want to intercept the exception before rethrow it (without AOP). Instead of having single Throwable catch block that rethrows the e, combined with the first issue, you will need to have 4 of them: 1 for each ExceptionA, B, C, plus another 1 to intercept and wrap Throwable. And it directly translates to 4 extra test-cases that offers no benefit but to reach that 3% coverage to meaningless boiler code that sits in the application code merely to shut the compiler up.
And hey, it has to be done in all method. Now I've long stopped doing unit-test to catch block except if it contains meaningful business logic. I.e., I let most of the code-block sit red on coverage report. Thus, almost none of my methods has 100% coverage.

Hendry Luk said...

... Now I've long stopped doing unit-test *on* catch-block ...

Something wrong with my english

 
Clicky Web Analytics