Jump to: navigation, search

Difference between revisions of "Riena/Sonar"

(Part 1)
(Introduction)
 
(7 intermediate revisions by the same user not shown)
Line 6: Line 6:
 
|}
 
|}
  
=Part 1=
+
=Introduction=
 
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.
 
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.
  
Line 14: Line 14:
  
 
But let's be serious for a second. What kind of common problems do we have in large multi-tier client-server projects?
 
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 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.
+
* 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.
+
* 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.
 
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:
+
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:
  
 
<source lang="java">
 
<source lang="java">
Line 47: Line 47:
 
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:
 
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:
  
[Image:Services.png]
+
[[Image:Services.png]]
  
 
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:
 
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:
Line 108: Line 108:
 
</source>
 
</source>
 
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:
 
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
+
* the database exists
you can connect to it
+
* you can connect to it
the table exists
+
* 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.
 
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.
  
Line 116: Line 116:
  
 
Ping'em all
 
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.
+
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 located in the Bundle org.eclipse.riena.example.ping.client.
  
=Part 2=
+
=Usage=
  
 
This is the second part of a two-part article on Ping. The first part gave you an instroduction to the Riena Ping API. This second part will show you the usage of the Sonar UI to run ping tests.
 
This is the second part of a two-part article on Ping. The first part gave you an instroduction to the Riena Ping API. This second part will show you the usage of the Sonar UI to run ping tests.
 
The first intention was to use ping for automated, JUnit-driven, integration tests. But that did not work out for two reasons:
 
The first intention was to use ping for automated, JUnit-driven, integration tests. But that did not work out for two reasons:
The build server supposed to run the tests was a unix system, but the client was targeted for windows.
+
* The build server supposed to run the tests was a unix system, but the client was targeted for windows.
The build server was located in the intranet and was not allowed to connect to the application server.
+
*The build server was located in the intranet and was not allowed to connect to the application server.
 +
 
 
During discussion of these problems, one of the infrastructure guys said: "Wouldn't it be cool, if we could use those ping tests as part of our daily system check?" Bingo! That's the idea. Instead of preparing a client-like test setup, just integrate the tests into the client. And that's what Sonar is all about: A UI dedicated for running ping tests, that could be easily integrated into any (Riena-based) client product:
 
During discussion of these problems, one of the infrastructure guys said: "Wouldn't it be cool, if we could use those ping tests as part of our daily system check?" Bingo! That's the idea. Instead of preparing a client-like test setup, just integrate the tests into the client. And that's what Sonar is all about: A UI dedicated for running ping tests, that could be easily integrated into any (Riena-based) client product:
 +
 +
[[Image:Riena_Sonar1.png]]
  
  
Line 130: Line 133:
  
 
So that's what Sonar basically does: If you press the start button...
 
So that's what Sonar basically does: If you press the start button...
it collects all services that implement the IPingable interface
+
* it collects all services that implement the IPingable interface
it creates a new PingVisitor and pings all services
+
* it creates a new PingVisitor and pings all services
it renders the result tree and provides any failure messages
+
* it renders the result tree and provides any failure messages
 
So usage is quite simple: Just run the tests. If everything is green, your system is elementary ok and you can start functional testing. If it's red, you have to analyze the failure message and see what's wrong. Let's do this by example. Take the following silly architecture from the first part:
 
So usage is quite simple: Just run the tests. If everything is green, your system is elementary ok and you can start functional testing. If it's red, you have to analyze the failure message and see what's wrong. Let's do this by example. Take the following silly architecture from the first part:
 +
 +
[[Image:Services.png]]
  
 
On the client side we have a couple of services. Some of them are local, others are stubs for remote services (dashed). On the server side, there are the service implementations for the client stubs, which itself may call other services, databases or other backend systems like e.g. a mail server. Now let's start a ping and see what happens:
 
On the client side we have a couple of services. Some of them are local, others are stubs for remote services (dashed). On the server side, there are the service implementations for the client stubs, which itself may call other services, databases or other backend systems like e.g. a mail server. Now let's start a ping and see what happens:
 +
 +
 +
[[Image:Riena_Sonar2.png]]
  
  
Line 147: Line 155:
 
Alright, in our silly example I just forgot about to start the server. But in reality, a couple of things could be the cause: Firewalls, wrong host names, network failure, whatever. But here the solution is quite simple: just start the server and try again:
 
Alright, in our silly example I just forgot about to start the server. But in reality, a couple of things could be the cause: Firewalls, wrong host names, network failure, whatever. But here the solution is quite simple: just start the server and try again:
  
 +
[[Image:Riena_Sonar3.png]]
  
 
Hmm, the remote services have been called successfully, but all pings to the database failed. What does the failure message say?
 
Hmm, the remote services have been called successfully, but all pings to the database failed. What does the failure message say?
Line 158: Line 167:
 
Oh, we've used a wrong database user. Smells like a stages problem. The developer has used the correct database user in development (stage), but forgot to set up the appropriate user for test, resp. productions. So let's fix the stage by setting up the correct DB user and try again:
 
Oh, we've used a wrong database user. Smells like a stages problem. The developer has used the correct database user in development (stage), but forgot to set up the appropriate user for test, resp. productions. So let's fix the stage by setting up the correct DB user and try again:
  
 +
[[Image:Riena_Sonar4.png]]
  
 
Aah, nicely green at last. So all system components from client to database are basically available now ;-)
 
Aah, nicely green at last. So all system components from client to database are basically available now ;-)

Latest revision as of 10:12, 16 March 2011

Introduction

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.

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:

Services.png

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 located in the Bundle org.eclipse.riena.example.ping.client.

Usage

This is the second part of a two-part article on Ping. The first part gave you an instroduction to the Riena Ping API. This second part will show you the usage of the Sonar UI to run ping tests. The first intention was to use ping for automated, JUnit-driven, integration tests. But that did not work out for two reasons:

  • The build server supposed to run the tests was a unix system, but the client was targeted for windows.
  • The build server was located in the intranet and was not allowed to connect to the application server.

During discussion of these problems, one of the infrastructure guys said: "Wouldn't it be cool, if we could use those ping tests as part of our daily system check?" Bingo! That's the idea. Instead of preparing a client-like test setup, just integrate the tests into the client. And that's what Sonar is all about: A UI dedicated for running ping tests, that could be easily integrated into any (Riena-based) client product:

Riena Sonar1.png


No need to say that it was inspired by the JUnit-Runner, eh? ;-) It consists of a single plugin (org.eclipse.riena.example.ping.client) which provides the Sonar Submodule and the main logic, and also menu and command handler for bringing up the module. The plugin is currently part of the Riena example application.

So that's what Sonar basically does: If you press the start button...

  • it collects all services that implement the IPingable interface
  • it creates a new PingVisitor and pings all services
  • it renders the result tree and provides any failure messages

So usage is quite simple: Just run the tests. If everything is green, your system is elementary ok and you can start functional testing. If it's red, you have to analyze the failure message and see what's wrong. Let's do this by example. Take the following silly architecture from the first part:

Services.png

On the client side we have a couple of services. Some of them are local, others are stubs for remote services (dashed). On the server side, there are the service implementations for the client stubs, which itself may call other services, databases or other backend systems like e.g. a mail server. Now let's start a ping and see what happens:


Riena Sonar2.png


Oops, all remote services are red. What's wrong?!? Let's have a look at the failure message:

org.eclipse.riena.communication.core.RemoteFailure: Error while invoking remote service at ... Caused by: java.net.ConnectException: Connection refused

Alright, in our silly example I just forgot about to start the server. But in reality, a couple of things could be the cause: Firewalls, wrong host names, network failure, whatever. But here the solution is quite simple: just start the server and try again:

Riena Sonar3.png

Hmm, the remote services have been called successfully, but all pings to the database failed. What does the failure message say?

java.lang.RuntimeException: java.lang.reflect.InvocationTargetException at
...
Caused by: java.sql.SQLNonTransientConnectionException:
Connection authentication failure occurred. Reason: Invalid authentication..

Oh, we've used a wrong database user. Smells like a stages problem. The developer has used the correct database user in development (stage), but forgot to set up the appropriate user for test, resp. productions. So let's fix the stage by setting up the correct DB user and try again:

Riena Sonar4.png

Aah, nicely green at last. So all system components from client to database are basically available now ;-)

Conclusion Sonar provides an easy way to run ping tests directly form your client product. The ping tests doesn't help you on testing business functionality, but give you a tool for checking basic integration and infrastructure problems... at almost no cost :-)