Wednesday 2 April 2008

Unit testing in a non-greenfield environment

Telecom/datacom OSS/NMS software has some features which make it differ from other types of projects.
  • Longevity : It could be actively used for a number of years
  • Uptimes: There are stringent uptime requirements(perhaps not for OSS/NMS)
  • Non-greenfield nature: Most projects rarely start from scratch, and most features are added on top of an existing framework.

Despite the stringent uptime & backward compatibility requirements for such software, Unit testing is often not an important part of the quality process. This is because:

1>Device interaction needs to be tested with a stub
  • This is usually difficult because devices are not small, their interfaces are significantly large
  • Sometimes devices themselves have bugs which the stub would have to mock
  • Device versions keep changing ,which would require the stub to constantly keep up.

2>Interactions with network elements are frequently asynchronous, rather than business process driven.

So what gives ?

The key idea is to start small, not mock part or whole of the device ,but smaller pieces.

Lets say we are testing a method "Method" in class A.

The code would be something like:

class A{
.....
private DBImpl db;
private NetworkImpl network;

public void method(TMessage message){
if(dbImpl.getABC()){
Val x = message.getVal();
if(network.Proto == UDP){
udpSendResult(x);

}
}
}


Looking at the above code, it seems impossible to test A.method() without DBImpl as well as NetworkImpl giving us the 'right' values.
At the same time, we can notice some changes in our design which makes it easier to test
  1. return values : methods that return values are easier to test . In this particular case, there is nothing that signals the 'success' of the method.
  2. Lack of interfaces : If DBImpl & NetworkImpl were interfaces, there is a possibility of making a separate test implementation.

We will return to other design changes when we hit them.

First steps in refactoring:

Eclipse has got some
excellent refactoring support, we shall use the 'extract Interface' part of it.

Open the class DBImpl in eclipse and right click->Refactoring->Extract Interface.
















Eclipse shows the difference of the changes after making the target class an implementation





Now that we have an interface, lets turn to this concept of mocking classes (or interfaces) .
Traditionally, to use the interface we just created, we would need to create an implementation tailored to our test. For e.g. if we need

if(dbImpl.getABC()){

to return true in one test, and false in another, its a time consuming affair. Instead, we use a concept called Mock Objects (another link) .

A tool like Jmock/Rmock allows us to create an implemetation of an interface, and define expectations from that implementation. This helps because

1>we only need to mock the method under test. If an interface has many methods, we dont waste time creating implementations for methods we dont need.

2>It allows fine grained control such as:
  • say that this method should be called N times, specify a different result each time
  • specify that if a method should ,or should not be called. (and so on).

A small snippet from JMock:

context.checking(new Expectations() {{
allowing(pcfr).getProvisioned();
will(returnValue(10)); 
allowing(pcfr).updateState();
}});


which says:
allow the 'getProvisioned()' method to be called, return 10;
allow the 'updateState()' method to be called.

Now that we have created behavior for the helper objects in this method, let us create a test.

In Eclipse, click on new->Junit TestCase , and enter the class under test.


We can choose which method need to be tested here.

If junit is setup correctly, the 'method' will pass under test.