Monday, July 13, 2009

Creating a jBPM process repository for unit testing with par files

My latest project involves general infrastructure improvements based on my experiences with jBPM. There have been many discussions about different aspects involving a generic process structure to enable the launching of new products in a more efficient, timely, and cost effective manner.

One of the more interesting elements in the general planning is to provide the development organization with a process repository. It will enable developers to leverage the existing Maven infrastructure to provide projects with exiting Process Archives, better known to jBPM developers as PAR files.

These files are similar to JAR files and contain the process definition along with any supporting Java classes needed to run. The interesting part is that you want to define simple process components (granularity is important) so that they can be leveraged through reuse. The idea then being that a new project can simply shop for existing process components and then import these exiting PAR files into a project where they plug into the project in process-state nodes (these being the jBPM sub-flows).

This requires that a simple process is the initial building block and needs to be setup with proper unit testing. For example, a simple process (which is left empty for posting purposes) that is used to transform input into some XML format via a single web service call. This call takes place in a state node, so you can see the process definition of the SuperProcess which calls the self contained SubProcess by fishing it out of the PAR repository included in the projects dependencies.

Here the SuperProcess definition:

<process-definition name="superprocess">

  <start-state name="start">
     <transition to="XML transform" name="to_process_state" />
  </start-state>

  <process-state name="XML transform">
    <sub-process name="xmltransform">
    <transition to="end" name="to_end" />
  </process-state>

  <end-state name="end" />

</process-definition>

The SubProcess definition:

<process-definition name="xmltransform">

  <start-state name="start">
    <transition to="end" name="to_end" />
  </start-state>

  <end-state name="end" />

</process-definition>

The unit test for the SubProcess (trivial in this example):

@Test
public void testXmltransformProcess() throws Exception {

  // Extract a process definition from the processdefinition.xml file.
  ProcessDefinition superProcessDefinition = 
    ProcessDefinition.parseXmlResource("xmltransform/processdefinition.xml");

  // Test it.
  assertNotNull("Definition should not be null", superProcessDefinition);

  // Setup superprocesses.
  ProcessInstance superProcessInstance = new ProcessInstance(superProcessDefinition);

  assertEquals(
    "Instance is in start state", 
    superProcessInstance.getRootToken().getNode().getName(), "start");

  // Move the process instance out of start state.
  superProcessInstance.signal();
  
  assertEquals(
    "Instance is in end state", 
    superProcessInstance.getRootToken().getNode().getName(), 
    "end");

  assertTrue("Instance has ended", superProcessInstance.hasEnded());

}

Now the unit test for the SuperProcess:

@Test
public void testSuperProcess() throws Exception {

  // Extract a process definition from the par file.
  ProcessDefinition subProcessDefinition = 
    ProcessDefinition.parseParResource("xmltransform.par");

  ProcessDefinition superProcessDefinition = 
    ProcessDefinition.parseXmlResource("superprocess/processdefinition.xml");

  // Test it.
  assertNotNull("Definition should not be null", subProcessDefinition);
  assertNotNull("Definition should not be null", superProcessDefinition);

  // Setup super and subprocesses.
  ProcessState processState = (ProcessState) superProcessDefinition.getNode("XML transform");
  processState.setSubProcessDefinition(subProcessDefinition);
        
  ProcessInstance superProcessInstance = new ProcessInstance(superProcessDefinition);
  ProcessInstance subProcessInstance   = new ProcessInstance(subProcessDefinition);
   
  // Get our process tokens.
  Token superToken = superProcessInstance.getRootToken();
  Token subToken   = subProcessInstance.getRootToken();

  assertEquals(
    "Instance is in start state",
    superProcessDefinition.getStartState(), 
    superToken.getNode());

  // Move the process instance from its start state to the first state.
  superToken.signal();

  // Subprocess in start?
  assertEquals(
    "SubProcess instance is in start state", 
    subProcessDefinition.getNode("start"), 
    subToken.getNode());
  
  // Move subprocess out of start.
  subToken.signal();
  
  // Subprocess ended?
  assertEquals(
    "SubProcess instance is in end state", 
    subProcessDefinition.getNode("end"), 
    subToken.getNode()); 
  assertTrue("SubProcess instance has ended", subProcessInstance.hasEnded());

  // Superprocess ended?
  assertEquals(
    "SuperProcess instance is in end state", 
    superProcessDefinition.getNode("end"), 
    superToken.getNode()); 
    assertTrue("SuperProcess instance has ended", superProcessInstance.hasEnded());

Of course these are really simple examples. We are looking into unit testing much more complex processes that include task-nodes, state-nodes, and processes with several layers (deep) of process-states. Anyone have experiences with these kinds of unit tests?

Note: these are jBPM v3.x processes.