Thursday, December 20, 2012

Testing More by Testing Less


Introduction


Recently I have been thinking about unittests an their utility. I've had the opportunity to try a different approach, which I want to talk about in this post.

We are all familiar with unittesting by now. For most of us, that means that for each class we write, a corresponding test is produced, preferably beforehand. However, I find this method brings a drawback: if you refactor that code, you must refactor your unittests as well. But as we all know, refactoring is good and should be encouraged.

Refactoring is needed because getting the design of the code right the first time is notoriously difficult. Sometimes this is due to poor judgement or time constraints. But the fact is that in practice it's hard to solve complex problems and have good design at the same time. And by 'good' I mean that the various classes and their assigned responsibilities are such that the code is easily understood and easily extensible.

The latter is particularly hard to achieve because, while we often think otherwise, we usually don't know what changes will be brought upon our application. That means spending more time refining the design will not help us, what would help is finding ways to change the code quickly and confidently when the need actually arises.

I feel this is not being addressed by the unitest-per-class method, because these kind of changes typically break the original APIs and/or introduce new ones in the form of new classes. That means your unittests are hit as well so they need to be updated, slowing you down and introducing more scope for errors. You are also less confident in the modified unittests than you would be if they could be reused unaltered.

That creates a barrier to refactoring code. Not only is there more code to refactor, but also you cannot rely on your unittests to protect you from errors. Which is ironic really, because this is a big reason why people write them in the first place. Meanwhile the bad code lingers, and when it must be touched because of defects, you would rather only fix that and get out as soon as possible. And each time you do that, the bad code grows. Wouldn't this be easier if the refactoring wasn't so risky or required such effort? 

There is another disadvantage: waste. Writing tests for all classes in your codebase is, in my opinion, not a good way to use resources. Because unittests are a lot like boilerplate: they don't really add any real value. They are only there to help us developers do our job, but they do not fulfil any business needs. In other words it is an activity which destracts us from achieving business value, and should therefore be limited.

Testing more by testing less

I propose that to effectively address these issues instead of writing a unittest per class, we test multiple classes collectively. This would give freedom in refactoring them, because your testcode would not use all classes directly, only a few necessary to input testdata and collect output.

I have pictured this below. The squares are classes, and the arrows denote the direction of inter class dependencies. The blue squares inside the dashed lines are the classes under test, or 'the system'. Squares outside the dashed lines are left out of the test, they effectively define the test boundary. 


The yellow classes need to be mocked, so that the tested classes have someone to talk to at runtime and won't crash. But also so that we can provide test data, and capture data sent from the system in order to assert their correctness and by extension the correctness of the system itself. There are many frameworks designed to help with these tasks, which we can reuse.

This is all quite similar to ordinary unittesting. You could even pretend that the classes being tested are just one class. Actually, we can do more than just pretend: we can introduce an intermediate class, which we use in writing tests instead of the real classes. I call this a TestProxy. It should be responsible for wiring the objects under test to the mock objects, and provide easy access methods for invoking real functionality on the objects under test, on a coarse grained level.

Let's consider a simple example based on the Model-View-Presenter (MVP) pattern. In this example the Presenter uses a a BusinessService for accessing business functions, this service in turn   accesses a resource service that fetches data from a remote location, like a webservice.

The View and the Resource class are good candidates for keeping out of the test: the Resource class needs a webservice running to work properly which makes testing cumbersome. And the View is usually not programmatically testable, often the reason for using the MVP pattern.

The picture below shows the classes involved in the test:




Note that I have drawn mocks for the View and Resource. These mocks need to be instantiated and wired to the actual Presenter and BusinessService, which happens in the TestProxy. It also makes these mockservices accessible to the TesterClass, because that is where the actual test logic is. The TesterClass selects mockdata, invokes functions through the TestProxy and does assertions.

That means that instead of talking to the Presenter or BusinessService in your tests, you only talk to mocks. This totally insulates the testlogic from the actual object hierarchy making this logic reusable should the hierarchy change. You should only have to change your test logic if there is some change in the mocks, which would only happen if the data shown on the view or fetched from the webservice changed.

Another advantage is that the TestProxy can provide an API that makes testlogic more readable and compact. We can achieve this in this particular case by mirroring the actions a user does in our API, because we mock views that interact with users directly. For instance, this is what some test pseudo code might look like:


function TestDeleteEntry(){
 
    staringEntriesList = { ... }
    endEntriesList = { ..... }
 
    startOverviewData = { ... }
    endOverviewData = { ... }

 
    TestProxy.ResourceMock.setEntries( startingEntriesList ); 

    TestProxy.start();
    TestProxy.MainMenuMock.assertHasFocus();

    TestProxy.MainMenuMock.chooseOverview();
    TestProxy.OverviewMock.assertHasFocus();
    TestProxy.OverviewMock.assertData( startOverviewData );

    TestProxy.OverviewMock.selectEntry(2);
    TestProxy.DetailViewMock.assertHasFocus();
    TestProxy.DetailViewMock.assertData( startingEntriesList.get(2) );

    TestProxy.DetailViewMock.chooseDelete();
    TestProxy.OverviewMock.assertHasFocus();
    TestProxy.OverviewMock.assertData( endOverviewData );


    TestProxy ResourceMock.assertEntries( endEntriesList );     

}

The exact API doesn't matter, and may well depend on the type of mocking libraries at your disposal. What matters is that the test logic does not depend on the internal wiring of the classes being actually tested. So maybe at first you implemented this using three views but the same presenter. Later you switch to three presenters. Than you decide to refactor the BusinessService, or the Model. Your test remains the same, and can therefore be trusted to catch any errors.

What matters also is that methods like chooseOverview and selectEntry relate directly to user actions. This makes it easy to write tests based on functional specifications, and working with testers in order to validate and design good tests.

We could further tweak this API, for instance turn the groupings of actions and assertions into methods themselves. But more importantly is reuse on another level. Suppose instead of deleting an entry, we would like to edit it? In order to get to the edit page, we would need to repeat some steps, may reuse some mock data. By creating methods which accomplish a reusable part of a flow, we can avoid any copy-and-paste. We can likewise structure our mock data into extensible sets.

Conclusion

I have argued that conventional unittesting methods have some flaws, and described a method for testing groups of classes instead of individual classes in order to address them. While there are some areas to work on, particularly reuse, after actually trying this method certain advantages are already clear to me.

As we have seen in the example, this method makes it possible to write tests on a level much closer to functionality observable to users, business level if you will. You can then work more closely with testers. Besides saving time in thinking of test data and test cases, this should also result in a higher quality of tests. This also means recreating defects found by testers is easier.

You can confidently refactor code with extensive freedom and more speed. Freedom and speed are good, but the confidence is also important. The tests are untouched, so they can be trusted to protect you in case of regression. If you had modified the original tests, you would have to verify that they work first.

I have also found that it gets relatively easier to reach a higher code coverage. This is due to the fact that a given input can trigger a cascade of objects calling each other, instead of you manually writing tests to call methods. But it is also because thinks like constructurs, getters/setters are tested automatically, and you would normally not test those.

This seams somewhat distorting and I besides the point. And to met testing constructors and getters/setters does not add much value. But I can think of one case where it might be usefull: dynamic languages. With dynamic languages you can't rely on the compiler to check for typing errors, so sloppy programming could easily cause a bug. The way to protect yourself is through a high unittest code coverage, where more of the code is exercised and such errors are exposed in the unitest.

The threshold to start testing does get higher, however, because you need to write more code in order to 'get testing' since we need to abstract the class hierarchy. While I think in the long run you need less code to achieve the same coverage, this does require some extra discipline. And as we all know this is often not easy given tight schedules and limited resources.

Finally, I think this method works best when you already have a modular architecture. That creates natural islands of related classes, and introduces loose coupling with other such islands. That means the amount of mocking gets minimized in relation to code tested, and you can truly 'test more by testing less'. You should ideally mock views, or a webservice call: these are more or less stable boundaries that won't change if the internal design of the code is altered.

I believe the testapi is not quite ready yet and warrants some more thought, in order to fully exploit the similarities with UC scenarios and promote reuse. Separating the test logic from the test data enables you to play different scenarios by changing data sets, this seems like a good strategy to increase test productivity so it's worth exploring further also.



Thursday, November 15, 2012

High Availability with JBoss SOA Platform


High Availability and why we need it

When employing ESBs, one of the most import aspects is that the ESB be highly available. This requirement will arise naturally from the fact that the ESB is an intermediate between other applications which rely on it to supply them with data. When the intermediate is offline, then the entire chain comes offline. To counter this, the ESB is deployed with some sort of failover mechanism in order to make it more resilient to failures.

JBoss SOA Platform is Red Hat's solution for enterprise SOA. It contains, among other things, the Jboss Application Server plus two products that we are interested in: JBoss ESB and JBoss Messaging. JBoss ESB is at it's core a mediation engine, it contains the logic for getting messages from one endpoint to another.  The endpoints are the sources and destinations for messages. They can be implemented with various technologies, like JMS queues, webservices, ftp sites, etc. JBoss Messaging is a JMS implementation, it enables you to setup and manage queues.

Combined this two technologies enable you to move data accross your enterprise: by letting an application put messages on one JMS queue, JBoss ESB can then transport it to another queue where the messages are picked up by another application. One of the advantages of such an approach is that the two applications that are actually sharing data in this way don't need to know about each other. They don't even need to be running at the same time. The downside, as previously noted, is that the ESB is a single point of failure.

HA strategies and concepts

We mentioned the need to make the ESB resilient to failures. But how do we go about this? Remember that the root of the problem is that the ESB is a single point of failure. That is the case because it is running in a process, and that this process could cease for whatever reason. 

But if we could have multiple ESBs running in their own processes, then the failure of one would not affect the other. The better the different processes are insulated from each other, the more robust the solution. For this to really work however, a mechanism would be needed so that in case of failure of one instance, the remaining ones take up the work it was doing.

But making the ESB resilient is not enough. Because the endpoints are the interface between the ESB and other systems, they too need to be HA for without them no data can be shared. The HA strategy of the endpoints is strongly correlated with the endpoint transport mode. For instance: JBoss Messaging can cluster queues, webservices can be made HA via a dedicated http loadbalancer. As it turns out JBoss Messaging has it's own out-of-the box clustering support, so we just use that.

The diagram below illustrates the ideas. The dashed lines represent process boundaries. The two ESBs are insulated from each other, so the failure of one does not influence the other. For simplicity we consider the case where an external agent (sender) puts messages into the messaging system via an endpoint which is by itself HA.
  



Note that the process boundary in which the HA endpoint is running arises because of failover. There are actually multiple instances on different processes, but they can work together and take over each other's work. So as far as the other systems are concerned, it is as if only one very resilient instance was running. Moreover, adding more ESB instances will increase performance, as long as there are enough messages to consume.

One question comes to mind: what happens when a ESB instance takes a message from the endpoint and then fails? If no precaution is taken, then the message is lost. A crucial feature of our HA implementation should be that each message processing is done inside a transaction, so that should it fail the message becomes available on the endpoint again for processing by still active instances.

JBoss Clustering and Messaging

Since we are using JBoss Messaging for our endpoint implementation, we should briefly explain how it's clustering works. JBoss Messaging can be made HA via JBoss Clustering, which is built into the JBoss Application Server. It uses the JGroups protocol to enable different JBoss instances on the same network to find each other and keep each instance aware of the others. Should one instance fail, then the others become aware of this and take over it's workload. JBoss Messaging uses this facility to detect a queue failure, and migrate it's messages to another running queue. 

In order to have our cluster we will deploy JBoss AS on different nodes on the same network. A node in this case would be a different (virtual) machine, which would do a good job insulating the different processes from each other. 







We consider a case with just one queue, Queue1, for simplicity. Queue1 is deployed on separate nodes, and clustered. As you can see JBoss ESB is deployed along with Queue1 on the same node, therefore that node's failure would affect both. Still we have a backup on the other node, so HA is achieved.

One thing to note is that the consumers of Queue1, including JBoss ESB, can directly communicate with any instance of Queue1 on any node in the cluster. This is because JBM does loadbalancing and failover on its JMS client implementation. This is actually the key of JBoss Messaging's HA, because without it clients would simply fail along with the failing node.

JBM clients are configured to contact one single node, from which they receive a list of all available nodes in the cluster which the node knows via JBoss Clustering. The client then can access the Queue1 instance on any of these nodes. Should it happen that the node which the client is currently connected to fails, then it will automatically switch to another node on the list, and access its Queue1 instance. The client can also loadbalance over the Queue1 instances, improving performance. This all happens transparently to the code that uses JBM, as far as that is concerned there is only one Queue1, and it never went down.

But what happens in the remote event that the complete cluster goes down? We still do not want to lose our messages. Therefore they are persisted by the queues in a separate storage system, shared by all nodes in this case. This database must of course be made HA also. The best strategy and implementation depends on the database vendor, and is beyond the scope of this article. We will assume a mysql database because it is a popular, proven product which can be made HA in a way transparent to clients.


Setting up an actual test  

Now that we have discussed some basic concepts, we can consider an actual case. For this example  the ESB will take messages from  a JMS queue named InBoundQueue and place it in the OutBoundQueue. Inside, we will configure the ESB to pause for a while, thereby simulating some heavy processing.





The nodes are virtualized using Virtualbox, or your favorite virtualization product. We will setup JBoss ESB and JBoss Messaging in HA configuration. Then we will consider a simple failover test and how to verify the results.

Virtualbox

Setup three virtual machines using your favorite OS, two for JBoss, one for Mysql. Make sure the virtual machines can resolve each other over the network by choosing for instance host-only networking in Virtualbox. Also, make sure the ip address of the mysql server is static, this depends of the OS you choose. It also makes things easier if the other nodes also have a static ip for deployment, sending messages, etc.

Install and setup the necessary software on the nodes, like mysql server and Java JDK. Copy JBoss soa platform on the JBoss nodes. I recommend setting up one JBoss node completely, then cloning it to create the other. The clone will need some customizing, see below.


JBoss Server Profile

First, create a new profile by creating a new directory under {soa.platform.install.dir}/jboss-as/server and copy into it the contents of the 'all' directory. I have chosen this one mainly because it contains the facilities necessary for clustering. Let's assume this profile is called 'my_cluster'.


JBoss setup

Then edit the file{soa.platform.install.dir}/jboss-as/tools/schema/build.properties according to your needs such as database vendor and connection info.

Take special care to set org.jboss.esb.server.config to the one created earlier, 'my_cluster'. Also make sure to set org.jboss.esb.clustered to 'true'. Also, copy the JDBC driver jar for your database vendor (for instance, mysql) to the lib directory of  'my_cluster' to avoid any errors further on.

Then, just run ant. The script will update the 'my_cluster' profile to work with your database and support clustering. 

JBoss will automatically use the credentials supplied to create the necessary tables for JMS.


JBoss startup

JBoss must be bound to the ip of the machine it is running on. Also, do not forget to start with the
jboss.messaging.ServerPeerID  option to a value unique inside your cluster, and to use the 'my_cluster' profile. There is also an option to set the cluster partition name jboss.partition.name. Nodes with the same name form a cluster when on the same network. This is useful for running multiple clusters on the same network, but does not concern us now.


JBoss ESB setup 

I will assume you are already familiar with developing and deploying .esb files. Edit jboss-esb.xml as follows:


<?xml version="1.0"?>
<jbossesb parameterReloadSecs="5"
 xmlns="http://anonsvn.labs.jboss.com/labs/jbossesb/trunk/product/etc/schemas/xml/jbossesb-1.2.0.xsd"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://anonsvn.labs.jboss.com/labs/jbossesb/trunk/product/etc/schemas/xml/jbossesb-1.2.0.xsd http://anonsvn.jboss.org/repos/labs/labs/jbossesb/trunk/product/etc/schemas/xml/jbossesb-1.2.0.xsd">
 <providers
  <jms-jca-provider connection-factory="ClusteredConnectionFactory" name="JBossMQ">
   <jms-bus busid="quickstartEsbChannel">
    <jms-message-filter dest-name="queue/EsbQueue"
     dest-type="QUEUE" persistent="true" transacted="true"/>
   </jms-bus>
   <jms-bus busid="GwChannel">
    <jms-message-filter dest-name="queue/InBoundQueue"
     dest-type="QUEUE" persistent="true" transacted="true"/>
   </jms-bus>
  </jms-jca-provider>
 </providers>

 <services>
  <service category="myCategory"
   description="Hello World File Action (esb listener)" name="myFileListener">
   <listeners>
    <jms-listener busidref="GwChannel" is-gateway="true" name="gwlistener"/>
    <jms-listener busidref="quickstartEsbChannel" name="helloWorldFileAction"/>
   </listeners>
   <actions mep="OneWay">
    <action class="org.jboss.soa.esb.actions.SystemPrintln" name="PrintBodyConsole">
     <property name="message" value="== Printing body =="/>
     <property name="printfull" value="false"/>
    </action>
    <action
     class="org.company.WaitAction" name="wait">
     <property name="timeSecs" value="60"/>
    </action>
    <action class="org.jboss.soa.esb.actions.SystemPrintln" name="PrintSendConsole">
     <property name="message" value="== Sending to Queue =="/>
     <property name="printfull" value="false"/>
    </action>
    <action class="org.jboss.soa.esb.actions.routing.JMSRouter" name="PutInOutQueue">
     <property name="jndiName" value="queue/OutBoundQueue"/>
     <property name="unwrap" value="true"/>
    </action>
   </actions>
  </service>
 </services>
</jbossesb>


In the provider configuration, note that we are using 'ClusteredConnectionFactory'. This connection factory makes sure we will access the clustered queues as one, and profit from failover and loadbalancing. Also note that we have set the transacted flag to 'true', so that messages will reappear on the queue if processing in the service pipeline fails. 

We also set the persistent flag to 'true', so that messagese will be preserved even when the whole cluster goes down. This amounts to letting the queue know we want persistent messages, it is up to the queue to actually provide this which it will since JBoss Messaging has been configured to work with Mysql.

Furthermore, note the WaitAction. This is a custom class which causes processing to pause for a configurable amount of time. This gives us time to kill the node during the pause in a controlled manner:

public class WaitAction extends AbstractActionLifecycle
{   
    private Long _timeSecs ;   
    private Logger _log;

    public WaitAction(ConfigTree tree) throws ConfigurationException {
        _timeSecs = Long.parseLong( tree.getRequiredAttribute("timeSecs") );
        _log = Logger.getLogger( this.getClass() );       
    }

    public Message process(Message message) throws InterruptedException {   

        _log.info("Going to sleep for " + _timeSecs +" secs."    );       
        Thread.sleep(_timeSecs.longValue()*1000);       
        _log.info("Awake, resume processing.");       
        return message;
    }
}

Don't forget to use the correct FQN for the class in jboss-esb.xml.


JBoss Messaging

We need to setup the required queues. Edit jbm-queue-service.xml als follows:


<?xml version="1.0" encoding="UTF-8"?>
<server>
      <mbean code="org.jboss.jms.server.destination.QueueService"
            name="jboss.esb.quickstart.destination:service=Queue,name=EsbQueue"
            xmbean-dd="xmdesc/Queue-xmbean.xml">
            <depends optional-attribute-name="ServerPeer">
                jboss.messaging:service=ServerPeer
            </depends>
            <depends>jboss.messaging:service=PostOffice</depends>            
            <attribute name="Clustered">true</attribute>
      </mbean>
      <mbean code="org.jboss.jms.server.destination.QueueService"
            name="mycluster.destination:service=Queue,name=OutBoundQueue"
            xmbean-dd="xmdesc/Queue-xmbean.xml">
            <depends optional-attribute-name="ServerPeer">
                 jboss.messaging:service=ServerPeer
            </depends>
            <depends>jboss.messaging:service=PostOffice</depends>           
            <attribute name="Clustered">true</attribute>
      </mbean      
      <mbean code="org.jboss.jms.server.destination.QueueService"
            name="mycluster.destination:service=Queue,name=InBoundQueue"
            xmbean-dd="xmdesc/Queue-xmbean.xml">
            <depends optional-attribute-name="ServerPeer">
                 jboss.messaging:service=ServerPeer
            </depends>
            <depends>jboss.messaging:service=PostOffice</depends>
            <attribute name="Clustered">true</attribute>
      </mbean>
</server>


We mark the queues as clustered by setting the 'Clustered' attribute.


Starting the cluster and deployment

To start the cluster, first start one JBoss instance on one of the Virtualbox machines and wait for it to be fully up and running. This will be what JBoss calls the 'primary node'. You should be able to see some logging similar to this:


2011-10-21 04:17:55,252 INFO  [org.jboss.ha.framework.interfaces.HAPartition.DefaultPartition] (main) Initializing partition DefaultPartition
2011-10-21 04:17:55,402 INFO  [STDOUT] (JBoss System Threads(1)-3)
---------------------------------------------------------
GMS: address is 192.168.56.101:55200 (cluster=DefaultPartition)
---------------------------------------------------------
2011-10-21 04:17:55,712 INFO  [org.jboss.cache.jmx.PlatformMBeanServerRegistration] (main) JBossCache MBeans were successfully registered to the platform mbean server.
2011-10-21 04:17:55,864 INFO  [STDOUT] (main)
---------------------------------------------------------
GMS: address is 192.168.56.101:55200 (cluster=DefaultPartition-HAPartitionCache)
---------------------------------------------------------
2011-10-21 04:17:58,410 INFO  [org.jboss.ha.framework.interfaces.HAPartition.DefaultPartition] (JBoss System Threads(1)-3) Number of cluster members: 1
2011-10-21 04:17:58,410 INFO  [org.jboss.ha.framework.interfaces.HAPartition.DefaultPartition] (JBoss System Threads(1)-3) Other members: 0
2011-10-21 04:17:58,414 INFO  [org.jboss.cache.RPCManagerImpl] (main) Received new cluster view: [192.168.56.101:55200|0] [192.168.56.101:55200]
2011-10-21 04:17:58,416 INFO  [org.jboss.cache.RPCManagerImpl] (main) Cache local address is 192.168.56.101:55200
2011-10-21 04:17:58,429 INFO  [org.jboss.cache.RPCManagerImpl] (main) state was retrieved successfully (in 2.57 seconds)


Then start the second node. You can see in the logging that they will find each other and form the cluster:


2011-10-21 04:20:55,432 INFO  [org.jboss.messaging.core.impl.postoffice.GroupMember] (Incoming-13,192.168.56.101:55200) org.jboss.messaging.core.impl.postoffice.GroupMember$ControlMembershipListener@32af3289 got new view [192.168.56.101:55200|1] [192.168.56.101:55200, 192.168.56.102:55200], old view is [192.168.56.101:55200|0] [192.168.56.101:55200]
2011-10-21 04:20:55,433 INFO  [org.jboss.messaging.core.impl.postoffice.GroupMember] (Incoming-13,192.168.56.101:55200) I am (192.168.56.101:55200)
2011-10-21 04:20:55,434 INFO  [org.jboss.messaging.core.impl.postoffice.GroupMember] (Incoming-13,192.168.56.101:55200) New Members : 1 ([192.168.56.102:55200])
2011-10-21 04:20:55,434 INFO  [org.jboss.messaging.core.impl.postoffice.GroupMember] (Incoming-13,192.168.56.101:55200) All Members : 2 ([192.168.56.101:55200, 192.168.56.102:55200])


After you have built your .esb file, deploy it in the farm directory of the primary node under my_cluster\farm. Watch in the logs as the queues are deployed on the primary and then secondary nodes.


Running some tests

In order to test failover have your cluster fully running, then send some messages to the inbound queue using your favorite tool, like Hermes JMS. Make sure the body of the messages are unique, so that they are easily identified in the logging. Then take one node offline and watch the logging on the other node. You should see the message appear there. 

When all processing is done, you should see all messages safe and sound in the outgoing queue. I prefer to inspect the database in order to validate this:
  

[root@jboss ~]# mysql jboss -e 'select HEADERS from JBM_MSG'
+-------------------------------------------------------------------------------+
|HEADERS |                  
+-------------------------------------------------------------------------------+
 OutBoundQueue  |ID:JBM-f36d6d66-9c2c-413e-a879-0510af996601 H.CORRELATIONID uickstartId[1321627299207] H.DEST
 
OutBoundQueue  |ID:JBM-9a448807-1911-475f-aa7f-661dfefa3164 H.CORRELATIONID uickstartId[1321627300266] H.DEST
 
OutBoundQueue  |ID:JBM-f83a3c80-31a7-484e-ba9a-e15eef316cc1 H.CORRELATIONID uickstartId[1321627302355] H.DEST



Conclusion

Clustering with JBoss is actually not that hard to setup because it is built into the JBoss Application Server, but the complexity of the solution can make the task seem daunting. I hope you now have some insight in HA concepts and implementation with JBoss SOA Platform, as well as how to verify that you have a working solution.