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 Eclipse Riena Project provides a little framework for writing and running such tests: the Ping API. The main interface is
The
Not that hard, is it? And for the lazy ones (like me ;-) there is the class
We already discussed the implementation. If you can derive, it is just
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:
When
Let's take the
The
But I don't use injection, I don't keep services in member variables. That's evil. I fetch service when I need 'em:
No problem. Just provide a method
If the
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
In
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
Regards
Ralf
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.
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() {
Listpingables = new ArrayList ();
pingables.add(Service.get(IRidiculousService.class));
...
return pingables;
}
If the
PingVisitor
finds a method matching that signature, it will ping all IPingable
s 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
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
Comments
Post a Comment