Wednesday, June 9, 2010

Give me a ping Vasily, Part I

Writing integration tests is not an easy task. Besides the business complexity, it is also often not easy to set up a suitable test scenario. This article will show an easy way to write integration tests for common problems in Eclipse based client-server projects.

It started in a meeting with project management. The leader of a large-scale SmartClient project asked: "How come we have so many unit tests, but only a few integration tests?". I guess the main problem was that the application was based on large, interweaved (host) database tables, and we were not allowed to alter any data...even in the test environment. So in fact, we could not set up any test data, we were forced to set up our test cases on the existing data.

But it was living data, and so it was subject to change. Means: even if you had set up a test based on that data, it was very expensive to maintain. The project leader claimed: "We need a kind of integration test that is easy to write, needs no maintenance and runs in any stage from development to production". That's three wishes at once. I ain't no Jeannie in a bottle, man ;-)

But let's be serious for a second. What kind of common problems do we have in large multi-tier client-server projects?
  • The components of the application are loosely coupled, so a lot of problems do not occur until runtime. Especially in a distributed development environment.
  • The environment-specific part of the application is extracted into stages. So if a developer is not careful, he might forget to provide the needed information in all stages.
  • Infrastructure: In a large distributed system there are a whole bunch of things that can go wrong, e.g. firewall problems, missing entries in the host file, etc.
So, if business-motivated integration tests are hard to write, maybe there's a way to write tests for the problems mentioned above? Well, the core of current multi-tier architectures is usually based on (local and remote) services. By successfully calling a service we can prove that...er, yo what?!? Well for local services it proves that the service is registered and the corresponding plugin is active. For remote service it also means, that we can connect from client to server. Services often need other resources to fulfill there job e.g. other services, databases, etc. If these resources are not available, the service itself is also not - or only partially - available. So our call to the service should also call all resources it depends on. And finally, if any call fails, the failure message will give you a hint on the cause.

The Eclipse Riena Project provides a little framework for writing and running such tests: the Ping API. The main interface is IPingable which defines a non-business service dedicated for implementing the test described above:
public interface IPingable {

PingVisitor ping(PingVisitor visitor);

PingFingerprint getPingFingerprint();
}

The ping() method defines the dedicated service call. The PingFingerprint is needed for reporting and to avoid cycles. So, any services that wants to get pinged has to implement that interface. The implementation is quite easy:
public PingVisitor ping(final PingVisitor visitor) {
return visitor.visit(this);
}

public PingFingerprint getPingFingerprint() {
return new PingFingerprint(this);
}

Not that hard, is it? And for the lazy ones (like me ;-) there is the class DefaultPingable which you can derive from. But here comes the tedious job: all service that wanna get pinged (that's usually ALL services) have to implement IPingable. Means all service interfaces must extends IPingable:
public interface IRidiculousService extends IPingable {

We already discussed the implementation. If you can derive, it is just
public class RidiculousServiceImpl extends DefaultPingable implements IRidiculousService {

Until now we've only pinged a single service. What about it's dependencies? What about resources like databases or backend server? Let's take the following silly architecture as an example:


On the client side we have a couple of service. Some of them are local, others (dashed) are stubs for remote services. On the server side there are the service implementation for the client stubs, which itself may call other services, a database or other backend resources like a mailserver. How are they getting pinged? That's the PingVisitors job. Remember the implementation of ping:
public PingVisitor ping(final PingVisitor visitor) {
return visitor.visit(this);
}

When visitor.visit(this) is called, the PingVisitor inspects the service for member variables that implement IPingable (using introspection), collects and then pings 'em.

Let's take the SeriousService from our silly architecture for example:
public class SeriousServiceImpl extends DefaultPingable implements ISeriousService {

private IRidiculousService ridiculousService;

@InjectService
public void bind(IRidiculousService ridiculousService) {
this.ridiculousService = ridiculousService;
}

public void unbind(IRidiculousService ridiculousService) {
this.ridiculousService = null;
}
...
}

The IRidiculousService is injected and stored in a member variable. On ping() the visitor will find the ridiculousService and pings it also. Means: ping() is called recursive on all IPingables found.

But I don't use injection, I don't keep services in member variables. That's evil. I fetch service when I need 'em:
public void doSomeRidiculousStuff() {
IRidiculousService ridiculousService = Service.get(IRidiculousService.class);
ridiculousService.dontWorryBeHappy();
}

No problem. Just provide a method getAdditionalPingables() that returns all IPingables you are interested in:
private Iterable getAdditionalPingables() {
List pingables = new ArrayList();
pingables.add(Service.get(IRidiculousService.class));
...
return pingables;
}

If the PingVisitor finds a method matching that signature, it will ping all IPingables in that Iterable. The drawback of this way is that you have to maintain this list of services.

Databases and other resources
By now we can ping all client and server side services. But what about other resources like databases or e.g. the mail server. Wouldn't it be nice if you could ping them also? Sure you can: The PingVisitor inspects the IPingable if there are methods called void ping...(), where the first character after ping must be an upper case letter e.g.
private void pingDatabase() {
...
}

In pingDatabase() you have to check if the database is available, e.g. make a select for a row with a certain ID. It doesn't even matter if that ID exists: If the select returns a result (that might be empty), it proves that:
  • the database exists
  • you can connect to it
  • the table exists
If that's not enough, you can write a stored procedure (named ping ;-) that can perform all kinds of checks. And your pingDatabase() method just calls the stored proc.

In the same way, you can check all kind of resources e.g. for a mail server you could check if a helo/quit succeeds.

Ping'em all
So now all services implement IPingable... but we haven't pinged anything yet. How do we do that? Well, you could write a JUnit test that collects all pingable services, creates a PingVisitor and calls ping() on them. Or you could use the helper class Sonar which does exactly that for you. Or... you could use the Sonar User Interface which will be introduced in the next part.

Regards
Ralf

No comments:

Post a Comment