Automating Business Rules Testing in Oracle SOA Suite 11g

I have already written two blogs earlier on different ways to test business rules in Oracle SOA Suite 11g. They can be accessed at the below links.

https://beatechnologies.wordpress.com/2011/04/19/using-and-testing-complex-business-rules-in-oracle-soasuite-11g/

https://beatechnologies.wordpress.com/2011/07/07/testing-oracle-business-rules-using-java/

The above posts describe strategies that can be employed to test business rules at both runtime and design time with a real world use case. The only hitch was in scenarios where developers would want to create a testing suite and use it along with a test framework like JUnit for a better and automated test coverage and analysis. The workaround was tedious and involved using the Business Rules API. This was also tedious as it involved a heavy bit of code to marshall xml request to JAXB objects.

However I felt compelled to write another post as the new SOA suite release has some better functionalities available out of the box as far as unit testing business rules is concerned. Starting from Oracle SOA Suite 11g PS4FP or PS5, we now have a much better support to test rules at design time, create test suite and write test cases in plain xml to cover all possible business scenarios.

Prerequisites

Downloading the BusinessRulesProject

This post builds up from the example and use case covered in the previous posts mentioned above. You can start by downloading the BusinessRulesApplication and open the .jws file in JDeveloper to open the project.

Working on the Test Suite

After you have opened the project in JDeveloper double click on the GradeAllocationRules component from the composite design view. This would open the rule dictionary in the rules editor.

Click on the Decision Functions tab on the left to see the web service function that wraps the rule sets within.  An icon image to create the test suite schemas is available that generates the XSD’s containing input and output types for a Test Suite.

image

You would not see anything happening in the wizard but you need not worry. The schemas are generated in the testsuites\xsd folder. A test suite is comprised of a number of test cases and each test case takes in an xml test input and an xml expected output.

image

You can then click on the the Test –> Test with Test Suite to now generate a Test Suite skeleton file.

image

If you create the Test Suite, you would wonder what happened. Since if you run the test suite from the Run Test button, it alerts that there is no input in your test case. Well if you go and see your project navigator, there will be a folder with the same name that is specified for the test case and a corresponding xml file within it. This xml file can contain as many unit test cases that you may want to run for the selected decision function. Each test case has an input and a corresponding expected output (an favorable output  that you need to assert with the response of decision function execution).

image

The immediate question is how to create a valid test case in the test suite xml file. The testCase element needs two elements viz testInput and expectedOutput in a format specified by the testCaseType. You can use any tool that can generate a sample XML from an XSD type in case you cannot figure it out yourself. Below is the way to do this using Altova. All that you would have to do is copy the child elements inside the testCase parent and replace their value with something that you can test your business rule with. Also note that you can have as many test cases created in this file as you may like.

image

Save the changes to the Test Suite xml file and repoen the Decision function test suite wizard. Click on the Run Test button to execute the business rule function against the input specified in the test case and assert it with a predefined response. This will show you the overall test suite execution status with outcome of all the individual test cases in a tabular format under the Test Results field box. The report tabulates all the test cases that are run, their execution status, execution trace and comments.

image  Now this is pretty good. There is no need to write any custom code to invoke the Rule API to create unit test cases. Developers can create more than one test suite and each test suite can contain any number of test cases.

Well all is good till now. But there are a couple of things that are missing here. First of all there is no option to save/export/archive the Test Results in the way it is displayed in the test wizard so as to refer them later. Secondly developers will still require a way to automatically run all the test suits rather than doing it manually. Proponents of automation will be will definitely find it important to apply a wider functionality such as automated builds, integration of test execution with continuous builds, etc.

Having said that, we can do a little bit of Java to invoke the run the test suite like the wizard does and save the output to a file. It is obvious that we will need to yet again use the Rules API but in a less messier way than before. All that we need to do is define the test suite with as many test cases with as many inputs and expected outputs and then have a test runner method to execute them.

Here in this following example I use a couple of methods from the DFTestSuiteRunner class to execute the test suite and DFTestSuiteResult class to obtain the execution summary.

These classes are available in the rulestest.jar located in <JDEV_HOME>\jdeveloper\soa\modules\ oracle.rules_11.1.1 directory. Create a java project and a java class in JDeveloper and add this jar into its classpath. I called my class as RuleTestSuiteRunner and here is its code:

package com.soatechnologies.sample.rules.testclient;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

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

import oracle.rules.sdk2.datamodel.DecisionFunction;
import oracle.rules.sdk2.decisionpoint.DecisionPointDictionaryFinder;
import oracle.rules.sdk2.dictionary.RuleDictionary;
import oracle.rules.sdk2.exception.SDKWarning;
import oracle.rules.testfmwk.common.exception.InvalidTestSuiteException;
import oracle.rules.testfmwk.impl.DFTestSuiteResult;
import oracle.rules.testfmwk.impl.DFTestSuiteRunner;
import oracle.rules.testfmwk.model.TestCaseType;
import oracle.rules.testfmwk.model.TestSuite;
import oracle.rules.testfmwk.util.TestUtil;

public class RuleTestSuiteRunner  {
public RuleTestSuiteRunner() {
}
/**
* This method  allows invoking the TestRunner utility to test an Oracle Business Rule Test Suite.
* The method also saves the result of the test into a HTML file that displays a JUNIT type static
* report.
*
* @param  dictionaryName  Absoulte path of the .Rules file
* @param  decisionFunction Name of the Decision Function to be tested
* @param  testSuiteName  Name of the of the Test Suire that is created in the rule test wizard
* @param  testSuiteFile  Absoulte path of the Rules test XML file. This is generated in the testsuites folder
* @param  reportDirectory File System directory where output of the test is to be archived
*/
public void testRulesTestSuite(String dictionaryName, String decisionFunction, String testSuiteName, String testSuiteFile, String reportDirectory) throws Exception {
File tsFile =null;
RuleDictionary rd=null;
DecisionFunction df=null;
String reportSummary= null;
try {
tsFile = new File(testSuiteFile);
}
catch(Exception fe) {
System.err.println("Exception in Reading Source Test Suite File: " + fe.getMessage());
}
if (dictionaryName != null && decisionFunction!=null)
{
rd=loadRuleDictionary(dictionaryName);
df = rd.getDataModel().getDecisionFunctionTable().getByName(decisionFunction);
}
//    System.out.println("DF Name : " + df.getName());
//    System.out.println("Dictionary Name : " + rd.getDataModel().getDecisionFunctionTable().getByName(decisionFunction));
DFTestSuiteResult dfTestSuiteResult = new DFTestSuiteResult(df.getName(),testSuiteName);
DFTestSuiteRunner testRunner= new DFTestSuiteRunner(tsFile,df,rd,dfTestSuiteResult);
List<String> listString = getTestCasesFromTestSuite(tsFile);
String[] testCaseArray = listString.toArray(new String[listString.size()]);
testRunner.runTests(testCaseArray);
reportSummary= dfTestSuiteResult.getResultSummary();
System.out.println("Test Case Output : "+ reportSummary);
saveTestReport(reportDirectory,reportSummary);
}
/**
* This method  returns an instance of the RuleDictionary object from the dictionary path
*
* @param  dictionaryPath  Absoulte path of the .Rules file
* @return  ruledictionary Object of the RuleDictionary Class
* @see         RuleDictionary
*/
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;
}
/**
* This method take in the Test Suite XML file that is generated with the Test Suite wizard, iterates over
*  the Rules test suite file and retuns a list of all test cases contained in it.
*
* @param  tsFile  Absoulte path of the .XML Test Suite file
* @return  testCaseArrayList List of all Test Cases in the Test Suite
*/
private  static List<String> getTestCasesFromTestSuite(File tsFile)  throws InvalidTestSuiteException
{
ArrayList<String> testCaseArrayList = new ArrayList<String>();
ListIterator localListIterator;
if (tsFile != null) {
TestSuite localTestSuite = TestUtil.readTestSuite(tsFile);
List localList = localTestSuite.getTestCase();
for (localListIterator = localList.listIterator(); localListIterator.hasNext(); )
{
TestCaseType localTestCaseType = (TestCaseType)localListIterator.next();
testCaseArrayList.add(localTestCaseType.getName());
}
}
return testCaseArrayList;
}
/**
* This method take in the Test Suite XML file that is generated with the Test Suite wizard, iterates over
*  the Rules test suite file and retuns a list of all test cases contained in it.
*
* @param  reportDirectory  Absoulte path of the file system directory to save the test run output
* @param  reportSummary  String containing HTML summary of all test case status
*/
private  static void saveTestReport (String reportDirectory, String reportSummary)  throws InvalidTestSuiteException
{
try{
// Create file
FileWriter fstream = new FileWriter(reportDirectory+"\\"+"TestReport.html");
BufferedWriter out = new BufferedWriter(fstream);
out.write(reportSummary);
//Close the output stream
out.close();
}catch (Exception e){
System.err.println("Error: " + e.getMessage());
}

}

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

RuleTestSuiteRunner testRunner = new RuleTestSuiteRunner();
String ruleProjectDirectory="C:\\JDeveloper\\mywork\\BusinessRulesApplication\\BusinessRulesProject"
+"\\oracle\\rules\\com\\obr\\sample\\app\\GradeAllocationRule.rules" ;
String decisionFunctionName="GradeAllocationRule_decideGrade";
String testSuiteName="GradeAllocationRulesTest";
String testSuiteXML="C:\\JDeveloper\\mywork\\BusinessRulesApplication\\BusinessRulesProject"
+"\\testsuites\\rules\\GradeAllocationRule\\GradeAllocationRulesTest.xml";
String reportDirectory="C:\\Arrun\\Reports";
testRunner.testRulesTestSuite(ruleProjectDirectory,decisionFunctionName,testSuiteName,testSuiteXML, reportDirectory);
}
}

Explaining the Code

Well, I have pretty much tried to use as much Java documentation as possible. Nevertheless, an object of the DFTestSuiteRunner class can be instantiated by passing the Test Suite file, Decision Function object, Rule Dictionary object and an object of type DFTestSuiteResult. All of these arguments can be constructed by simply either passing their fully qualified names or absolute path in the file system.

The getTestCasesFromTestSuite(File testSuiteFile) method a list of all test cases available in a given test suite. The DFTestSuiteRunner object can then be used to invoke the runTests(java.lang.String[] p1) method that unit tests all the test cases coming as an array of strings.

And finally you can use the DFTestSuiteResult object to print the test execution report summary and archive it to a file. The class contains a getResultSummary() method that returns an html string containing the execution report in the same tabular format as displayed in the test wizard.

Here is a sample of executing the main function of this class with all appropriate arguments being specified.

image

And the archived HTML report can be found in the directory location specified for the report output directory. It contains the test case assertion status and the execution trace. If there are any tests that fails the reason is specified in the comments column.

image

This code is much easy to implement and maintain. This is generic and practically constant. All you have to do is cover as many test scenarios to test your business rules as test cases inside the test suite xml file.

You can find the entire BusineRulesApplication containing the Rule composite and the Java project from here.

Also feel free to suggest any changes or improvements if there are any.

8 thoughts on “Automating Business Rules Testing in Oracle SOA Suite 11g

  1. Pingback: Rhymiaq

  2. Pingback: resume examples for free

  3. Hi Arun,

    I am working on a project with the below requirement.

    Requirement:
    Update or Create Buckets,BucketSets and Java Facts in the existing Rules 11g.

    Is it achievable by Rules API.
    Any sample project if achievable.

    I referred the below links for the JAVA api.
    http://docs.oracle.com/cd/E23943_01/apirefs.1111/e10663/toc.htm
    http://docs.oracle.com/cd/E11036_01/web.1013/b28965/sdk.htm#BJFHJBIC

    I referred the Rules SDK previously but could not able to update or create the Buckets,BucketSets and Facts because the api’s doc which was provided by Oracle was not so informative and could not able to find the correct method which will be used for the creation and updation of rules through java.

    Kindly help me on this………….

    Like

    • Hi Ganesh,

      I am not too sure about the rule API allowing you to change bucketsets, facts etc but I vaguely remember that it is possible somehow. I will try to dig more into this. Meanwhile can you tell me what is the strong reason for being able to do it through Java API’s and not through the runtime composer which is pretty good.

      Like

      • Hi Arun,

        Thanks for your reply……..

        Requirement is that we need to sync the rules from BMI(Big Machines) application which has the master data for rules.BMI will provide an xml file with rules data to Fusion then Fusion with the help of java rules api need to create or update the rules in Oracle business rules.

        I am able to create a rule with if/then model with rules api but I could not able to create the rule with DecisionTabel model.Suggest approach for this requirement is to use Decision Tables only.

        Kindly help me on this road block.

        Like

  4. Pingback: Unit Testing Business Processes in Oracle BPM Suite 11g | Oracle Technologies Premier

  5. Pingback: Industrialized SOA – Where are you? – Part 1 | Rubicon Red

  6. Pingback: Industrialized SOA - Where are you? - Part 1 | Rubicon Red

If you have any comments, suggestions or feedback about the post, please feel free to type it here and I will do my best to address them asap

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s