LinkedIn

Thursday, July 7, 2011

Testing Oracle Business Rules using Java

Foreword

In my previous blog post on Creating and Testing Complex Business Rules in Oracle SOA Suite 11g, I have  pretty much covered how we can create a fairly complex rule and test it in multiple ways. In the same article I have explored how we can Test business rules at design time using RL functions or use Enterprise Manager to test them. We have also seen as how rules in Oracle SOA Suite can be exposed as standard soap service and thus can be invoked by multiple parties.

However one practical problem in using these above approaches is that we may have Business Rules that might run into hundreds of assertions and rule conditions that may prove to be a nightmare to test.

So how do we go about it. One elegant way would to create Test cases for business rules in SOAP UI and test the rule service like we generally test any other soap services. SOAP UI provides a wonderful framework for testing soap based services where in we can use assertions on the responses and even print test reports.

Another approach and an old school one is to write Java classes to invoke your Business rules. Oracle Business rules can be instantiated and invoked using Java code as well. This opens up the possibility of creating JUnit type test cases and test suites for your Business rules and test them extensively. This way we can further use any code coverage tool also to see how much of the rules are we testing already.

In the following article I will show how we can write a custom Java class to invoke and test a Business rule created in Oracle SOA Suite. For the purpose of this tutorial I will use the same example and code as used in the previous article about Oracle Rules. You can find the article at this link and even download the source code.

Prerequisites

Oracle JDeveloper 11g with SOA Suite Extensions

The Solution

Download the Business rules sample from the previous tutorial from the above link and Open it in JDeveloper.

Create a New Java Project inside the same application i.e BusinessRuleApplication wherein we can create our Java class to run our business rules.

image

image

Now Create a new Java Class called RuleTester in the Java Project that has been created.

image

image

To create a Java Class that can initialize and invoke Oracle Business rules we would need to add the standard Oracle Rules jars to our project’s classpath. Add the following libraries to the project dependencies as shown in the figure.

image

Also note that when we create any Business rules in Oracle SOA Suite 11g in JDeveloper it creates the JAXB classes for them. Make sure that even these classes are added to the project classpath.

image

The libraries to be added are also mentioned in the table below.


Oracle Rules Editor Component
Oracle Rules Dictionary Component
Oracle Rules
JDeveloper Runtime
Java EE 1.5
Oracle JDBC
ADF Model Runtime
ADF DVT Faces Databinding Runtime
Trinidad Runtime 11
Oracle JEWT

We are good now as far as creating the Project and adding rules libraries is concerned. Now let us jump on to creating the Java code to invoke and test the rule.

The code used to test the decision function created in the rule is given below.

package com.beatechnologies.sample.rules.testclient;

import com.wordpress.beatechnologies.gradeallocation.CandidateInformationType;
import com.wordpress.beatechnologies.gradeallocation.ObjectFactory;
import com.wordpress.beatechnologies.gradeallocation.CandidateGradeType;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import oracle.rules.sdk2.decisionpoint.DecisionPoint;
import oracle.rules.sdk2.decisionpoint.DecisionPointBuilder;
import oracle.rules.sdk2.decisionpoint.DecisionPointDictionaryFinder;
import oracle.rules.sdk2.decisionpoint.DecisionPointInstance;
import oracle.rules.sdk2.dictionary.RuleDictionary;
import oracle.rules.sdk2.exception.SDKWarning;

public class RuleTester {
public RuleTester() {
super();
}

private DecisionPointInstance decPointInstance = null;

// Initialize the Rule Tester Class
public void initialize(String ruleDictionary, String decisionFunctionName) throws Exception {

if (ruleDictionary == null || decisionFunctionName == null)
throw new Exception("Enter rule dictionary location and decision service name for Rule Engine to successfully initialize.");
// Load Decision Point using Dictionary on File System
DecisionPoint decPoint =new DecisionPointBuilder().with(decisionFunctionName).with(loadRuleDictionary(ruleDictionary)).build();
decPointInstance = decPoint.getInstance();
System.out.println("Rule Tester Class is now Initialized");
return;
}

// Loads the rule dictionary from the specified dictionaryPath to return a rule dictionary object
private static RuleDictionary loadRuleDictionary(String dictionaryPath) throws Exception {

RuleDictionary ruledictionary = null;
Reader reader = null;
Writer writer = null;
try {
reader = new FileReader(new File(dictionaryPath));
ruledictionary = RuleDictionary.readDictionary(reader, new DecisionPointDictionaryFinder(null));
List<SDKWarning> warnings = new ArrayList<SDKWarning>();
ruledictionary.update(warnings);
if (warnings.size() > 0) {
System.err.println("Rule Dictionary returned the followingv validation warnings: " + warnings);
}
}
finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ioe)
{
ioe.printStackTrace();
}
}
if (writer != null) {
try {
writer.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
return ruledictionary;
}

// Executes the Rule Decision Function by taking candidateInformationlist input and return the results.
public List runRules(ArrayList candidateInformationInputList) throws Exception {

List<Object> candidateGradeList = null;
if (decPointInstance == null)
throw new Exception("RuleTester not intialized.");
System.out.println("Running Rules");
if (candidateInformationInputList != null)
{
decPointInstance.setInputs(candidateInformationInputList);
// invoke the decision point with Candiate Marks Information
candidateGradeList = decPointInstance.invoke();
if (candidateGradeList == null || candidateGradeList.isEmpty())
{
System.out.println("RuleTester: No results returned by Rules Service");
}
else
System.out.println("RuleTester: " + candidateGradeList.size() + " result(s) returned.");
}
return candidateGradeList;
}

// Creates a test data candidate Information object for input to the rule engine which is an XML type Fact.
public static CandidateInformationType createTestData() throws JAXBException {

// Create the sample input XML as string
String candidateMarksInformation =
"<exam:CandidateInformation xmlns:exam=\"http://beatechnologies.wordpress.com/GradeAllocation\">\n" +
"  <exam:name>Arun</exam:name>\n" +
" <exam:rollNumber>7000001</exam:rollNumber>\n" +
"<exam:class>X</exam:class>\n" +
" <exam:section>SCIENCE</exam:section>\n" +
" <exam:remarks>Good</exam:remarks>\n" +
"<exam:subject>\n" +
"<exam:subjectName>ENGLISH</exam:subjectName>\n" +
"<exam:subjectCode>001</exam:subjectCode>" +
"<exam:subjectMark>85</exam:subjectMark>" + "</exam:subject>" +
"<exam:subject>" +
"<exam:subjectName>GEOGRAPHY</exam:subjectName>" +
"<exam:subjectCode>002</exam:subjectCode>" +
"<exam:subjectMark>85</exam:subjectMark>" + "</exam:subject>" +
"</exam:CandidateInformation>";

CandidateInformationType candidateInformation = null;

// XML Facts are represented by JAXB types.
// Use the Generated types to parse an xml test message into an JAXB element
JAXBContext jaxbContext = JAXBContext.newInstance("com.wordpress.beatechnologies.gradeallocation");
Unmarshaller unMarsh = jaxbContext.createUnmarshaller();
ByteArrayInputStream is =new ByteArrayInputStream(candidateMarksInformation.getBytes());
Object obj = unMarsh.unmarshal(is);
JAXBElement jaxbElement = (JAXBElement)obj;
candidateInformation = (CandidateInformationType)jaxbElement.getValue();

// Print Candidate Information input to stdout by marshalling it back to xml
System.out.println("Candidate Information Avaliable Is:");
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
marshaller.marshal(obj, System.out);

return candidateInformation;
}

// Main funtion to Test the Business Rule as a Java class in JDeveloper

public static void main(String[] args) throws Exception {

RuleTester tester = new RuleTester();

// Load the Rules Dictionary, provide location of .rules file and Decision Function name
tester.initialize("C:\\JDeveloper\\mywork\\BusinessRulesApplication\\BusinessRulesProject\\" +
"oracle\\rules\\com\\obr\\sample\\app\\GradeAllocationRule.rules","GradeAllocationRule_decideGrade");

// Create an array of inputs that match the CandidateInformationType xml used in the  Rules Decision Function
ArrayList candidateInformationInputList = new ArrayList();
candidateInformationInputList.add(createTestData());

// Execute the Rules passing in the candidateInformation input
List candidateGradeList = tester.runRules(candidateInformationInputList);

if (candidateGradeList != null) {
//Candidate Grade Object returned from rules decision function
CandidateGradeType result = (CandidateGradeType)candidateGradeList.get(0);

// Print Candidate Grade XML returned from Rules Engine to stdout
JAXBContext jaxbContext =JAXBContext.newInstance(CandidateGradeType.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
ObjectFactory objFactory = new ObjectFactory();
marshaller.marshal(objFactory.createCandidateGrade(result), System.out);
}
}
}
On a high level here is what the code does

Initializes the Decision function inside the Rule project. We have to provide the absolute path to .rule file that gets created in the Rules project directory and also the name of decision service.

image

It then reads a hard coded xml string that is a mocked input to the decision service. However we cannot pass a string content to the rule function. We have to unmarshall the xml string into a JAXB object before we can send it to the service.

You can see more about JAXB (Java Architecture for XML Binding) at the below link


We invoke the business rule service and get the response in a list. Simply take the first child of the list and marshall it into an xmlstring from a JAXB object.

Also write some sysouts to display the input as well as the output to the console.

We are now ready to run this Java class and test the rule. Remember we do not need to have a server runtime for this. We can simply run this as a simple java program.

image

After the Java class compiles and is executed we can see the following output in the console

image

We can now create a JUNIT test suite to read from multiple input files and assert the rule results. This approach would be very beneficial if we have hundreds of rules created in a composite and we require a through testing for them.

The Java Project created for the tutorial can be downloaded from here.

No comments:

Post a Comment