Complex Decision Making using DMN in Oracle Process Cloud Service

In a previous blog post, I wrote about the new Decision Modeling capability introduced in Oracle Process Cloud Service. The blog provided an introduction to DMN and a usage scenario along with a working prototype.

The decision modeling and notation standard released by OMG is a very powerful framework providing an implementation and modeling specification for solving many complex operational and strategic decision in an organization. In this second blog post of the series, I will put the DMN engine in Oracle PCS through a litmus test by implementing a complex decision modeling use case.

To make matters more interesting, I have now selected a decision modelling scenario published as an open challenge in the Decision Management Community in February. The solution shared in this post has been accepted and validated by the community moderator.

The modeling approach shared in this blog is based on the principle of “Inherent Simplicity” which says that every situation, no matter how complex it initially looks, is exceedingly simple. I chose this use case as it shows how principles of decision modeling can allow us to break a layered problem into simpler and easy to implement fragments.

At the end of the post, I will also provide a link to where you can download the sample application. Buckle up, read on and enjoy!

The decision modeling and notation standard released by OMG is a very powerful framework providing an implementation and modeling specification for solving many complex operational and strategic decision in an organization.

Problem Statement

The problem statement was shared on the Decision Management Community website in February 2017 and an abstract of it is presented here:

Your decision model should determine potential fraud during an online product booking process. It’s based on an automatically calculated Fraud Rating Score that should be less than 200 to allow automatic booking. Fraud Rating Score depends on the following factors:

  • If Booked Product Type is POST-PAID-HOTEL add 5 to the score
  • If Booked Product Type is INTERNAL-FLIGHT add 100 to the score
  • If Booked Product Type is INTERNATIONAL-FLIGHT add 25 to the score
  • If Booked Product Type is CAR add 10 to the score
  • If Booked Product Type is PRE-PAID-HOTEL add 5 to the score
  • If there were no previous orders from this customer add 100 to the score
  • If Number of Orders from this customer between 1 and 10 including bounds add (100 – Number of Orders * 10) to the score
  • If Customer has previous disputes add 190 to the score

Solution

Decision Requirements Diagram

The problem statement can be broken down into different decision elements each producing outcomes which can roll up to provide the final interpretation. Since we are leveraging Decision Modeling and Notation to solve this, the complete solution is presented in form of both a decision model diagram and a decision logic representation. 

To evaluate the overall scenario as part of this use case, the overall decision model is broken down into decisions and sub decisions as shown below:

Determine Booking Fraud DRG

The decision requirements diagram shows how the desired outcome can be achieved by combining modular sub decisions explained here:

  • Determine Booking Fraud is the top level decision that asserts a Boolean flag of true|false as a final interpretation of the sub decisions. The flag is based on the Compute Fraud Score  decision value (true for fraud score greater than 200 and false for fraud score less than 200 )
  • Check Active Disputes is a sub decision that iterates over all Dispute elements of a booking request and if any dispute is valid asserts a constant fraud score (190 in this case)
  • Check Past Orders is a sub decision that iterates over all Order elements of a booking request and if there are any past orders,  asserts a calculated fraud  score (100 – Number of Orders * 10)
  • Calculate Product Type Fraud Score is a sub decision that loops through all Products in a booking request and based on the product type, assigns a fraud score. It also rolls up a sum of all assigned fraud score for each product type.
  • The Compute Fraud Score sub decision invokes the above sub decisions and sums up the evaluated fraud score from each.

Input Data Definition

In order to implement the decision logic for this scenario, we would need to create a root level Booking request input data type. The data structure for the decision model is depicted below:

InputData

  • A Booking can have one or more Products. Each product has a unique productId. A product element also has a sub-element representing the productType.
  • A Booking can have one or more Orders. Each order has a unique orderId. The bookingState sub element represents if it is a current order or a completed one.
  • A Booking can have one or more Disputes. Each dispute has a unique disputetId. The disputeState sub element represents if it is a confirmed or an active dispute. Active disputes are currently under investigation.

Decision Logic Level

As I stated in my previous blog, decision requirements diagram do not convey how the decision is implemented. This is done through decision logic elements. The overall decision model is broken down into individual decisions that are modular with the top level decision determine if the booking is a fraudulent one or not. The sub decisions uses different types of boxed expression to compute a fraud score for every scenario covered in the use case.

I started the solution implementation in Oracle PCS by creating a new Decision Model application. Then added input data elements based on the structure of the Booking element described earlier.

Booking Type

The final implemented decision logic was aligned to the decision requirement diagram. Here is a teaser of the final decision model showing the input data elements and the individual decision logic elements.

Overall Decision Model PCS

The top level decision and each of the underlying decisions is explained in more details in the following section:

Decision 1 – Check Active Disputes

Create a decision in the decision model editor using the configuration option provided here.

Decision Type Sub Decision
Boxed Expression Type If-then-Else
Decision Input Type Booking.Disputes[] (List)
Decision Output Type Dispute Fraud Score (Integer)
Question Does the booking request has any fraud score based on an unresolved disputes?
Allowed Answers 190,0

Check Active Disputes

The expression simply checks through an If-Then-Else construct if the count of Dispute elements in the Booking request is greater than 0. If a dispute is found (positive number count), then the decision assigns a fraud score of 190 else the fraud score is 0.

Decision 2 – Check Past Disputes

Create a decision in the decision model editor using the configuration option provided here.

Decision Type Sub Decision
Boxed Expression Type If-then-Else
Decision Input Type Booking.Orders[] (List)
Decision Output Type Dispute Fraud Score (Integer)
Question Does the booking request has a fraud score based on an any past orders?
Allowed Answers 0-100

Check Past Orders

The conditional expression checks if the count of Order elements in the Booking request is greater than 0. If a previous orders are found (positive number count), then it uses the number of order to determine the fraud score using the below formula.

100 – Number of Orders * 10

The higher the number of previous orders, the lesser is the fraud score. For example, if a booking has 9 previous orders, then the fraud score would be 10 as opposed to a fraud score of 90 if a booking has 1 past order.

Decision 3 – Calculate Product Fraud Score

Create a decision in the decision model editor using the configuration option provided here.

Decision Type Sub Decision
Boxed Expression Type Function with a Decision Table
Decision Input Type Product Type (String)
Decision Output Type Dispute Fraud Score (integer)
Question What is the fraud score for a particular product type?
Allowed Answers 0,5,25,100,10,5

This is a tricky sub decision to implement. There can be multiple products within a booking request and a fraud score has to assigned based on the product type for each product.

The sub decision is implemented as a function (a boxed expression that creates a parameterized logic to apply multiple times with different parameter values). The function accepts productType as an input and evaluates the fraud score based on a unique hit policy decision table.

Calculate Product Type Fraud Score

Needless to say, this function has to be invoked from another sub decision by passing the appropriate parameter value.

Decision 4 – Loop Through Products

Create a decision in the decision model editor using the configuration option provided here.

Decision Type Sub Decision
Boxed Expression Type Friendly Enough Expression Language (FEEL)
Decision Input Type Booking.Products[] (List)
Decision Output Type Fraud Score List (integer)
Question
Allowed Answers

The sub decision is implemented as a friendly enough expression language (FEEL) to loop over the Products in a booking and return a list of fraud score for each product type. This sub decision invokes the parameterized Calculate Product Fraud Score by passing the product type for each product in the loop.

Loop Over Products

Decision 5 – Compute Fraud Score

Create a decision in the decision model editor using the configuration option provided here.

Decision Type Sub Decision
Boxed Expression Type Friendly Enough Expression Language (FEEL)
Decision Input Type Booking
Decision Output Type Overall Fraud Score (integer)

This sub decision is again implemented as a friendly enough expression language (FEEL) to sum all the product scores determined by the previous decisions. It uses a summation function to calculate the sum of all product type fraud score retrieved from the Loop through Product decision and adds the result of the Check Past Orders and Check Active Disputes decisions.

Compute Fraud Score

Decision 6 – Determine Booking Fraud

Create a decision in the decision model editor using the configuration option provided here.

Decision Type Sub Decision
Boxed Expression Type If-then-Else
Decision Input Type Computed Fraud Score (integer)
Decision Output Type Booking Fraud Interpretation (boolean)
Question Does the computed fraud score confirm it is a fraudulent booking?
Answers True, False

The conditional expression checks if the value of the computed fraud score is greater than or less than 200. If it is greater than or equal to 200, then it asserts true otherwise false.

Determine Booking Fraud

This completes the implementation of the decision logic.

Creating a Decision Service

A decision model can expose multiple decision services which in turn are comprised of a combination of decision functions. I created a single decision service with an operation determineBookingFraud by associating the top level decision Determine Booking Fraud to it. After the decision model is deployed to the infrastructure, the decision service is available as a REST endpoint.

Services

Testing the Final Decision Model

The following section shows how the overall solution is unit tested. It also shows how the decision model is exposed as a decision service rest operation which can accept a JSON type request and assert an outcome. But before that, there is another powerful feature in Oracle PCS which allows design time unit testing the decision mode, that I want to talk about.

Unit Testing – Testing for Fraud

Request

Click on the blue arrow icon icon on your decision model to access the unit testing console. This will open a dialog that allows entering input data based on the defined data type. Let us say that for a fraudulent booking scenario, we enter the following data elements:

Rule Test Input

Response through Sub Decision Chains

When the decision model is executed, all the sub-decisions would execute too. The result for each of the decision functions for the above request through the decision functions would be:

Check Active Disputes 190
Check Past Orders 90
Calculate Product Fraud Score 5,100
Loop Through Products
Compute Fraud Score 385
Determine Booking Fraud true

Rule Test Output

Decision Service Testing

The decision model can also be deployed as a service which can be invoked as a REST POST operation (determineBookingFraud) from any external application or process.

Scenario 1 – Non Fraud Scenario

Request
 {
 "Booking": {
 "bookingId": "123213213",
 "product":
 [
 {
 "productId": "13213213",
 "category": "POST_PAID_HOTEL"
 },
 {
 "productId": "634344",
 "category": "INTERNATIONAL_FLIGHT"
 }
 ],
 "order":
 [
 {
 "orderId" : "12312214",
 "orderType" : "Hardware",
 "orderState" : "Completed"
 }
 ],
 "Dispute":
 [
 ]
 }
 }
 Response
 {
 "interpretation": false
 }

Rule Test Service - Non Fraud Scenario

The total score from the different combinations of products, orders and disputes is:

Products: 5+25=30; Orders:  90; Disputes: 0
Total: 120

The Calculated Fraud Score is 120 which is less than the threshold value of 200 and hence it is not a case of fraud.

Scenario 2 – Fraud Scenario

Request
 {
 "Booking": {
 "bookingId": "123213213",
 "product":
 [
 {
 "productId": "asdads",
 "category": "POST_PAID_HOTEL"
 }
 ],
 "order":
 [
 {
 "orderId": "1232",
 "orderType": "New",
 "orderState": "Progress"
 }
 ],
 "Dispute":
 [
 {
 "disputeId": "123213213",
 "disputeState": "Active"
 }
 ]
 }
 }
 Response
 {
 "interpretation": true
 }

Rule Test Service - Fraud Scenario

The total score from the different combinations of products, orders and disputes in this scenario is:

Products: 5; Orders: 90; Disputes: 190
Total: 285

The Calculated Fraud Score is 285 which is greater than the threshold value of 200 and hence it is a fraud.

Summary

This post covered a glimpse of the true power of decision modeling notation to model and implement real world complex decisions. It also provided a preview of the different types of boxed expressions available in the PCS decision modeling engine and how to create and combine them to create complex decisions.

The sample project can be downloaded from here.

In the previous blog, I explained how to get started with decision modeling and its meta-model through a simple use case. The blog post can be read here:

https://beatechnologies.wordpress.com/2017/05/09/introduction-to-decision-model-and-notation-in-oracle-process-cloud-service/

In the next blog post in this series, I will show how to work with lists and collections in Decision Model through another use case. If you have any comments, suggestions or feedback, please do not hesitate to share it here.

Unit Testing Business Processes in Oracle BPM Suite

One of the fundamental challenges that every individual/team experiences when implementing business processes using Oracle BPM Suite 11g is the ability to run unit tests. Managers and testers direly want unit testing from developers to ensure code quality and stability. Another important requirement around testing is around coverage i.e how to ensure that all possible scenarios represented by the business processes are properly unit tested. Having a stringent methodology towards unit testing and a framework of executing them every time a  functionality is changed or when the business processes are deployed is a much desired and essential feature. Unfortunately there is a dearth of information available for developers to be able to create a complete and comprehensive framework of unit testing. As such, a lack of this knowledge is a big deterrent and this blog will, in details, cover the soup to nuts of planning, creating and executing a unit testing methodology that provides an almost cent precent coverage of all scenarios in a business process.

The Problem Statement

The credibility of a business process lies not only in the way it has been implemented but also in it being diligently tested for all outcomes and scenarios. Business processes implemented with BPMN 2.0 posses a greater challenge as processes tend to be fairly unstructured and involve a lot of components like decision functions, workflow tasks, events and services to name a few. As such it is very challenging to ensure that the processes being implemented are fully tested and hundred per cent compliant with how they are intended to function. It is no surprise that without a concrete unit testing the QA phase of the project is marred with several issues and this is when you start to feel that a lot of bugs could have been spotted and fixed during the development phase if there had been some sort of mechanism to test various routes of a process map. To add to the woes, I have seen many projects either performing these tests manually as a series of steps per test case or trying desperately to create a complex framework of using API’s to initiate a process instance, obtain and complete task items, stub service components etc.  The problem with both these approaches are that they are time consuming and relatively prone to errors. Because every project is usually always on a tight schedule, these tests are not executed and the issues are not spotted during the development cycle. Another option is to use various off the shelf testing suites available in the market but they tend to be expensive, have a steep learning curve and may not always fit the the bill.

This article take a unique approach that employs the existing unit testing framework available with Oracle BPM Suite 11g together with proven scientific software testing techniques to achieve an almost full coverage of unit tests for business processes.

The Sample Business Process

In order to demonstrate the philosophies expressed earlier and the testing techniques to follow , this article considers a simple employee expense approval process that has primarily all the ingredients that any complex business process has i.e it is fairly unstructured, have business rules, human workflow components, arbitrary cycles, gateways and events.

The process is fairly easy to understand. There are two actors in this process viz. Expense Approver (typically a manager) and a Finance Approver. An expense approval request event is evaluated against a business rule to determine whether it can be automatically approved. If so, it is sent to a finance admin to review and disburse payment if everything is right and green. If the expenses over-reach the auto approval limits and/or if the finance admin rejects them, they then go to the manager who then have to approve or reject them. Only manager can reject an expense for it to be recorded as rejected. The finance approver can only refer it back to the manager if he finds it to be incorrect. Well, this may not be the ideal and most comprehensive expense management process, but for the purpose of this demonstration, this variation is fairly suitable considering the commonalities.

ExpenseApproval

With the problem statement laid out and the sample business process being considered, the challenge is how can we guarantee that the different routes in this process can be thoroughly unit tested. 

Prerequisite(s)

The modelling workspace and the version of Oracle BPM Suite used in this demonstration is 11gR1PS5 i.e 11.1.1.6 although the concepts discussed are generic and applicable to all versions. I have also used MS-Visio for the simplified block diagrams but any diagramming tool can be used instead.

Testing Methodology

The testing methodology being discussed here is holistically categorized into five different steps, with each steps being characterized by a distinct set of functions and objectives.

Structuring

The first step is the structuring step wherein the process map needs to have a finalized structure with all scenarios being identified and modeled including the exception paths. The structuring step may begin with the definition of the highest level business process diagrams such as process choreographies or collaboration diagrams, etc. (https://beatechnologies.wordpress.com/2011/10/17/choreography-collaboration-and-oracle-bpm-11g/) but it is to be considered complete when the BPMN models are finalized and approved.

Reconstruction

This step is generally required if the business process is not straight forward and a plain vanilla type which is unusual in most cases. A reconstruction of the BPMN model is necessary to be able to evaluate the possible number of routes in the process map. The processes are simplified to be represented as block diagrams to uniquely identify all actors, events and sequence flows only with each of them marked generically. A couple of guidelines to reconstruct are:

  1. Represent each activity, event and sequence flow with a distinct name to convey the process flow (RULE for decision activity, SERVICE for sync/async communications with external systems, initial trigger and final outcome for events, approval actions for sequences etc.). You may use alphabets as suffixes if there are more than one similar activities.
  2. Optionally use colour coding for sequence flows to represent happy and alternative paths in a business process.
  3. Use markers in sequence flow to indicate its execution ability (parallel, conditional, inclusive etc.) with respect to other sequence flow from the same activity.

The following diagram represents a simplified skeleton for the expense approval business process with bare minimum semantics. As you can see that the model generalizes the name of each activity/event/sequence in the business process. A default and a conditional marker is also used to represent the execution flow path from a given node. 

SimplifiedExpenseApprovalV1

There is just one more step to reconstruct the diagram further. For as much as possible, try to group the likely marked activities together to make the diagram look simpler.  The final reconstructed diagram should ensure that each activity has uniquely different sequence flow originating out of it. If there are more than one similar sequence flow that originates from an activity then group them as one. For instance there are two default sequence flow originating out of the FINANCE activity and leading to the service activities SERVICE C and SERVICE D. These activities have to be combined/grouped to represent a single activity and there should just be one default sequence flow leading out of the FINANCE activity into it. The simplified and final reconstructed model would look like the one shown below.

SimplifiedExpenseApprovalV2

Evaluation

Having a simplified and reconstructed model is a significant milestone as it is required to measure the number of linearly independent routes in a business process. This is a very essential piece of information to derive at the minimum number of test cases required to unit test the business process to ensure it has a full coverage. To determine the minimum number of test cases required to cover all possible process routes for the expense approval business process, McCabe’s Cyclomatic Complexity algorithm can be applied.  Cyclomatic complexity can be computed using the reconstructed control flow diagram of the business processes. The Basis Path Testing by McCabe who first proposed it, is to test each linearly independent path in the business process; in this case, the number of test cases will equal the computed cyclomatic complexity.

The cyclomatic complexity, M, is evaluated as:

M = S – A + 2E

where

 S = the total number of sequences (edges) in the restructured process diagram
A = the number of events and activities (nodes) in the process
E = the number of end events in a business processes (connected components)

Looking at the restructured process map for expense approval will reveal that there are 10 sequences and 9 nodes (6 activities, 3 events) and 2 end events. Hence the maximum number of test cases required for full test coverage can be determined as:

M = 10-9+2*2 = 5

It is now easy to determine the test cases to be executed by a casual inspection of the reconstructed process graph. The following test cases shows the various routes a process can take from the conditional nodes.

Test Case 1: Submit, Auto Approve, Finance Approve, Approve
Test Case 2: Submit, Auto Approve, Finance Reject, Refer
Test Case 3: Submit, Refer, Manager Approve, Finance Approve, Approve
Test Case 4: Submit, Refer, Manager Approve, Finance Reject, Refer
Test Case 5: Submit, Refer, Manager Reject, Reject

Another important use of McCabe’s number (cyclomatic complexity calculation) is to understand and limit the complexity of a business process. It is recommended that the complexity of a business process should not exceed the figure of 10. If it does then it is advisable to split it into multiple processes.

Process Implementation

The expense approval business process implementation is not covered in this blog but readers are free to create an expense business object, define the conditional logic, auto generate UI for the task forms and so forth. If you want to spare yourself from that effort then an implemented process can be downloaded from here. The implemented process looks like below in the studio canvass.

image

Test Suite Creation

From a unit testing perspective, there are two things that should be predominantly tested. One is the business process itself and all its possible paths. This has been explained already and a strategy defined. Another thing that is important to test is the business rule component.  Well the approach to be taken depends upon the nature of the rules and their effect on the business processes. If the business rule is merely to dynamically determine a condition that defines the process path (followed by an exclusive gateway) then testing the business process should cover it. However, if the business rules executes conditions to create dynamic process data, then testing the business processes is not sufficient. A more thorough unit testing of the business rule must also be carried out.

The below three blog posts cover testing of business rules extensively by a wide variety of means.

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/

https://beatechnologies.wordpress.com/2012/03/09/automating-business-rules-testing-in-oracle-soa-suite-11g/

This blog intends to build upon the strategies covered in the above posts but also refine it further to create automated tests that can be executed using Ant or Maven and a detailed JUnit type report published for them.

Unit Testing the Business Process

There may be many strategies to test the business process in an end to end manner but this post focuses primarily on unit testing. This is more appropriate is scenarios where a testing has to be carried out by possibly stubbing out behaviours of external systems that the business process is dealing with. A readily available and out of the box tool to test a business process is the composite test framework. But the interesting challenge is how this framework can be used to test a business process with complex human tasks, events, business rules, etc. and this is something that is aimed here.

  • Begin by creating a test suite by right clicking on the testsuites folder under SOA Content in JDeveloper. Name this test suite as ExpenseApprovalTest. This will create a folder by this name and some auto generated contents within them.

image

  • Next right click on the generated test suite and click Create Test to create a unit test case for the composite. It is advisable to put an apt name and description for the test case so that it explains the scenario being tested. This will generate an .xml file by the test case name in the tests sub-directory.

image

  • Double click the .xml file to open it in JDeveloper. The editor opens the test view of the composite with all the services, components and references. This test view allows to create pretty sophisticated unit tests by providing the following options.
    • Define Initiate Messages to start the business process if it is based on a message based start. Else initiate events or the Initiate task can be used to invoke a manually triggered business process.
    • Each wire leading from the service, component and reference can be configured with Wire Actions. The wire actions can emulate the response from the target component if the component is not implemented. The wire action can also assert expected response(s) with the actual response or the emulated response from the output message of target components. Multiple assertions can also be configured on the same wire actions.

image

  • Double click on the binding element at the ApproveEmployeeExpense service. This launches the Initiate Messages wizard. Generate a sample message that conforms to the request type that this service expects.

image

  • The generated sample has dummy values which can be replaced with actual test values. In this case, use the following sample request.  This message is used to invoke the submit expense application start event message in the business process.

Initiate Message

<submitExpenseApplication xmlns="http://www.rubiconred.com/ApproveEmployeeExpense">
<Expense xmlns="http://www.rubiconred.com/bpmtest/empl">
<Employee>
<EmployeeID>1312331</EmployeeID>
<FirstName>Arrun</FirstName>
<LastName>Pareek</LastName>
<Email>arun.pareek@rubiconred.com</Email>
<PhoneNumber>0424987673</PhoneNumber>
<ManagerID>6312313</ManagerID>
<DepartmentID>132313</DepartmentID>
<BusinessUnit>1</BusinessUnit>
</Employee>
<ExpenseList>
<Amount>5500</Amount>
<Type>Travel</Type>
<Date>2013-02-06</Date>
<Justification>Travel on Business</Justification>
<Authoriser>Matt</Authoriser>
</ExpenseList>
<ExpenseList>
<Amount>4000</Amount>
<Type>Meals</Type>
<Date>2013-02-06</Date>
<Justification>Meals</Justification>
<Authoriser>John</Authoriser>
</ExpenseList>
</Expense>
</submitExpenseApplication>

  • As the message arrives in the business process, an expense record is created in the database with an initial status. The operation to insert a message in the database is one-way and hence from the testing perspective, it is good enough to assert what is going in. A valid assertion for unit testing for this test case is to validate the expenseTotal element in the input request and match it to the sum of all expenses in the expense list.

image

  • The next step in the business process is the business rule task and the response (output) from the business rule can be asserted by modifying the wire action between the business process and the business rule. At the moment the business rule is configured to respond back with a status of pre-approval i.e whether an approval from the manager is required or not. This test case assumes that the manager approval is not required as the values in the expense item list determine the rule output. This can be asserted against the expected response. The business rule is configured to auto approve expenses less than 10k for employees in the Sales business unit Sales (determined by the integer value of 1).
  • At this point the business rule component can be opened to see how it processes the employee expense fact and what is the response action.

    image

  • As mentioned before, it is recommended to have a detailed unit testing suite created solely for business rules as they may or may not determine the process path. Also the business process testing should be based on the rule outcome which is either Approved or Referred. But the business rule outcome can depend upon a lot of factors. For the example considered here, the business rule vets the outcome differently for different business units i.e the auto approval threshold is different depending upon the business unit the employee belongs too. Hence it is advisable to test the rules separately. A quick approach to do so will be discussed here shortly.

  • The next activity that will be executed in this test case is the Administer Expense Payment human task assigned to the Finance admin. Till this point all the activities being asserted against will automatically produce a result/outcome. But a human task is a manual step in the business process that is performed by a process participant. Hence the way to emulate a human task response in a test case is tricky. Fortunately the test composite allows emulating call-backs for human tasks that can mock task execution.

  • Begin by double clicking on the wire between the business process and FinanceApproval human task component. This, as usual, launches the wire action wizard. Select initiateTask from the list of operation to emulate its call-back. The call-back should be emulated when the task has been completed and hence the callback operation to be chosen is onTaskCompleted.

Generating a sample for the call-back operation creates a workflow task request based on the task schema. It is complicated to create values based on this schema initially so it is advisable to get a generated one and then use it while creating the test case. All values in the task xml are unimportant except for the values within the payload element under the root and outcome element under systemAttributes. The payload content corresponds to the data object (s) defined for the human tasks and is used for passing the values to the process variables. Whereas the outcome is used to determine the business process flow beyond the human task.

image

  • Repeat the exact above steps for the updateTaskOutcome operation too. Behind the scenes when a human actor logs into the task workspace the following things happen.
    • Depending upon the workspace preference and role a list of tasks are displayed to the user. The queryTasks operation is used with the proper predicate for this purpose.
    • When a task item is selected the getTaskDetailsById function is executed to retrieve the details specific to the selected task.
    • All updates, captures and saves to the task in the workspace are persisted in the dehydration store via the updateTask operation.
    • When the task is finally actioned its outcome and state is updated via the updateTaskOutcome operation which sends a callback to the waiting business process instance about task completion.
  • The operations discussed here were just a high level overview of what happens to a human task in the general scenario. There are infinite other possibilities of what stages the human task may go through in terms of assignments, renewals, expiries etc. However the business process does not care about all that. It simply waits for a callback from the human task which happens either when the task is actioned or when it expires.
  • Emulating the callback for the updateTaskOutcome will pass the control back to the business process and it will then process ahead depending upon the task outcome. The task outcome for this test case is set to Approved.

RuleAssert2

  • The following request can be used to emulate the task responses for this project. The outcome determines what is the action performed on the task where as the payload corresponds to what the user sees in the screen and conforms to.

Task Response Payload

<task xmlns="http://xmlns.oracle.com/bpel/workflow/task">
<title>Approve Expenses for Arun Pareek</title>
<payload>
<ExpenseApproval xmlns="http://www.rubiconred.com/bpmtest/empl">
<Expense>
<ns2:Employee xmlns:ns2="http://www.rubiconred.com/bpmtest/empl">
<ns2:EmployeeID>1312331</ns2:EmployeeID>
<ns2:FirstName>Arrun</ns2:FirstName>
<ns2:LastName>Pareek</ns2:LastName>
<ns2:Email>arun.pareek@rubiconred.com</ns2:Email>
<ns2:PhoneNumber>0424987673</ns2:PhoneNumber>
<ns2:ManagerID>6312313</ns2:ManagerID>
<ns2:DepartmentID>132313</ns2:DepartmentID>
<ns2:BusinessUnit>1</ns2:BusinessUnit>
</ns2:Employee>
<ns2:ExpenseList xmlns:ns2="http://www.rubiconred.com/bpmtest/empl">
<ns2:Amount>2500</ns2:Amount>
<ns2:Type>Travel</ns2:Type>
<ns2:Date>2013-02-06</ns2:Date>
<ns2:Justification>Meals</ns2:Justification>
<ns2:Authoriser>Matt</ns2:Authoriser>
</ns2:ExpenseList>
<ns2:ExpenseList xmlns:ns2="http://www.rubiconred.com/bpmtest/empl">
<ns2:Amount>4000</ns2:Amount>
<ns2:Type>Meals</ns2:Type>
<ns2:Date>2013-02-06</ns2:Date>
<ns2:Justification>Meals</ns2:Justification>
<ns2:Authoriser>John</ns2:Authoriser>
</ns2:ExpenseList>
</Expense>
<ApprovalStatus xmlns:ApprovalStatus="http://www.rubiconred.com/bpmtest/empl">
<ApprovalStatus>Approved</ApprovalStatus>
<Comments>Expenses Approved</Comments>
</ApprovalStatus>
</ExpenseApproval>
</payload>
<taskDefinitionURI>default/ExpenseApprovalComposite!4.0/FinanceApproval</taskDefinitionURI>
<ownerRole>ExpenseApprovalProject.ProcessOwner</ownerRole>
<priority>2</priority>
<identityContext>jazn.com</identityContext>
<processInfo>
<instanceId>290011</instanceId>
<processId>ApproveEmployeeExpense</processId>
<processName>ApproveEmployeeExpense</processName>
</processInfo>
<systemAttributes>
<acquiredBy>weblogic</acquiredBy>
<approvalDuration>31958</approvalDuration>
<approvers>weblogic</approvers>
<assignedDate>2013-02-01T01:24:03+11:00</assignedDate>
<createdDate>2013-02-01T01:24:03+11:00</createdDate>
<customActions>
<action>APPROVE</action>
<displayName>Approve</displayName>
</customActions>
<customActions>
<action>REJECT</action>
<displayName>Reject</displayName>
</customActions>
<digitalSignatureRequired>false</digitalSignatureRequired>
<displayInfo>
<applicationName>worklist</applicationName>
<hostname>RubiconRed-PC</hostname>
<httpPort>7001</httpPort>
<httpsPort>0</httpsPort>
<uri>/workflow/FinanceApproval/faces/adf.task-flow?_id=FinanceApproval_TaskFlow&amp;_document=WEB-INF/FinanceApproval_TaskFlow.xml</uri>
</displayInfo>
<endDate>2013-02-01T01:24:34.958+11:00</endDate>
<hasSubTasks>false</hasSubTasks>
<inShortHistory>true</inShortHistory>
<isGroup>true</isGroup>
<numberOfTimesModified>4</numberOfTimesModified>
<outcome>APPROVE</outcome>
<passwordRequiredOnUpdate>false</passwordRequiredOnUpdate>
<pushbackSequence>INITIAL_ASSIGNEES;1-4</pushbackSequence>
<secureNotifications>false</secureNotifications>
<shortHistory>
<task>
<state>ASSIGNED</state>
<updatedBy>
<id>workflowsystem</id>
<type>user</type>
</updatedBy>
<updatedDate>2013-02-01T01:24:03+11:00</updatedDate>
<version>1</version>
<versionReason>TASK_VERSION_REASON_INITIATED</versionReason>
</task>
</shortHistory>
<state>COMPLETED</state>
<systemActions>
<action>UPDATE_COMMENT</action>
</systemActions>
<systemActions>
<action>SKIP_CURRENT_ASSIGNMENT</action>
</systemActions>
<systemActions>
<action>VIEW_SUB_TASKS</action>
</systemActions>
<systemActions>
<action>DELEGATE</action>
</systemActions>
<systemActions>
<action>SUSPEND</action>
</systemActions>
<systemActions>
<action>UPDATE</action>
</systemActions>
<systemActions>
<action>UPDATE_ATTACHMENT</action>
</systemActions>
<systemActions>
<action>SUSPEND_TIMERS</action>
</systemActions>
<systemActions>
<action>ESCALATE</action>
</systemActions>
<systemActions>
<action>VIEW_PROCESS_HISTORY</action>
</systemActions>
<systemActions>
<action>REASSIGN</action>
</systemActions>
<systemActions>
<action>VIEW_TASK</action>
</systemActions>
<systemActions>
<action>CUSTOM</action>
</systemActions>
<systemActions>
<action>OVERRIDE_ROUTING_SLIP</action>
</systemActions>
<systemActions>
<action>INFO_REQUEST</action>
</systemActions>
<systemActions>
<action>WITHDRAW</action>
</systemActions>
<systemActions>
<action>ACQUIRE</action>
</systemActions>
<systemActions>
<action>VIEW_TASK_HISTORY</action>
</systemActions>
<taskId>f17ceabd-7c23-431d-b538-3697fed9d421</taskId>
<taskNumber>200507</taskNumber>
<updatedBy>
<id>weblogic</id>
<displayName>weblogic</displayName>
<type>user</type>
</updatedBy>
<updatedDate>2013-02-01T01:24:34+11:00</updatedDate>
<version>4</version>
<versionReason>TASK_VERSION_REASON_COMPLETED</versionReason>
<taskDefinitionId>default/ExpenseApprovalComposite!4.0/FinanceApproval</taskDefinitionId>
<taskDefinitionName>FinanceApproval</taskDefinitionName>
<workflowPattern>Participant</workflowPattern>
<isTestTask>false</isTestTask>
<participantName>default.DefaultPerformer</participantName>
<assignees>
<id>FinanceApprover</id>
<displayName>FinanceApprover</displayName>
<type>application_role</type>
</assignees>
<rootTaskId>f17ceabd-7c23-431d-b538-3697fed9d421</rootTaskId>
<systemStringActions>PUSH_BACK,sharePayload</systemStringActions>
<isTemplateTask>false</isTemplateTask>
<taskViewContext>Action Required</taskViewContext>
<taskNamespace>http://xmlns.oracle.com/UnitTestingSample/ExpenseApprovalProject/FinanceApproval</taskNamespace>
<actionDisplayName>Assigned</actionDisplayName>
<timers/>
<componentType>Workflow</componentType>
<activityName>Administer Expense Payment</activityName>
<activityId>ABSTRACT_ACTIVITY580105091431</activityId>
<thread>0</thread>
<parentThread>-1</parentThread>
<swimlaneRole>FinanceApprover</swimlaneRole>
<timersSuspended>false</timersSuspended>
</systemAttributes>
<systemMessageAttributes/>
<callback>
<id>ApproveEmployeeExpense/HumanTasks.FinanceApproval.reference</id>
<converstationId>urn:DD2C56306BB111E28F475966BC8DFF84</converstationId>
</callback>
<isPublic>false</isPublic>
<percentageComplete>100.0</percentageComplete>
<sca>
<applicationName>default</applicationName>
<componentName>FinanceApproval</componentName>
<compositeDN>default/ExpenseApprovalComposite!4.0*soa_2673adb8-4e08-4734-a459-5715871259d6</compositeDN>
<compositeInstanceId>460011</compositeInstanceId>
<compositeName>ExpenseApprovalComposite</compositeName>
<compositeVersion>3.0</compositeVersion>
<ecId>1b7e5955c26b51de:3e996a9d:13c906f8522:-8000-00000000000044e4</ecId>
<parentComponentInstanceId>bpmn:290011</parentComponentInstanceId>
<parentComponentInstanceRefId>290011-ABSTRACT_ACTIVITY580105091431-ApproveEmployeeExpense_try.2-7</parentComponentInstanceRefId>
<compositeCreatedTime>2013-02-01T01:20:09.947+11:00</compositeCreatedTime>
</sca>
<applicationContext>OracleBPMProcessRolesApp</applicationContext>
<taskDefinitionId>default/ExpenseApprovalComposite!3.0/FinanceApproval</taskDefinitionId>
<correlationId>f17ceabd-7c23-431d-b538-3697fed9d421</correlationId>
<mdsLabel>soa_2673adb8-4e08-4734-a459-5715871259d6</mdsLabel>
<customAttributes/>
</task>

  • Post approval by the finance admin the last step in the process is saving the payment record in the database. This also being a one way operation, from the unit testing perspective add a test check to ensure that the payment amount being saved is equal to the sum of all amounts in the payment list.
  • This is done by adding an assertion in the wire between the RecordEmployeeExpense component and PaymentRegisterService reference in the test composite view.

image

  • Business processes ending with a message end event are asynchronous in nature (in most cases) where as the ones ending with an empty end events are one way. The ExpenseApprovalProcess is an asynchronous business process as it has an message based end event that sends the final status of the approval request. This status can also be asserted to test if the clients invoking the business process get a valid business response. The final assertion in the test case.
  • Response from the business process can be validated by asserting the payload of the call-back operation expensesPaid. The expected value of the approvalStatus should be FINANCE APPROVE for the given test case which can be entered in the assert value text field.

image

Apart from the unit testing steps covered, there is no harm in adding more test conditions. This has to be determined on use case basic and quality of unit testing to be produced. Also add as many test cases as derived according to McCabe’s number.

Similarly test cases have to be added to test each possible path in the business process as determined by the cyclomatic complexity algorithm. Alternatively download the composite to see all the test cases created for this exercise.

Unit Testing the Business Rule (s) Components

As explained earlier, it is recommended to not mix business rule testing and business process testing together. As you can see that there are only two possible approval statuses from the cascaded business rules below. The business process takes a due transition only based on the business rule outcome, i.e, the approval status. However the approval status could itself be derived in a lot of ways depending upon the business unit of the employee submitting the expense and also the sum of the expense amount.

image

To create using testing for business rules, I would recommend going through my previous blog that explains this in great details. The blog can be accessed here:

https://beatechnologies.wordpress.com/2012/03/09/automating-business-rules-testing-in-oracle-soa-suite-11g/

One missing piece to the puzzle however is to to plug the testing framework with ant or maven so that they can be run as part of the build and deployment cycle. This is briefly discussed in this blog too.

Test Execution

Using Enterprise Manager

The easiest way to invoke the unit test suite is by deploying the composite and then initiating the testing from the enterprise manager console.

  • Start the soa/bpm managed server and deploy to the composite to the default partition.
  • Login to the enterprise manager and then navigate to the ExpenseApprovalComposite under the deployed partition.
  • Clicking on the Unit Tests tab will show all the created test cases under the test suite. All or some of the test cases can be selected and then click the Execute button.
  • This will prompt for naming the test run. Provide a name and then click OK to start the testing.

image

  • Once the tests have finished executing the Test Runs displays the final status of the test run along with statuses of each test case. A detailed report is also available in the trail showing how each assertion fared. The Enterprise Manager offers a great view in terms of determining what the actual value of a given step was against its expected value. It also does a full XML compare to show the difference between expected and actual XML structures.
  • This is a great way to test/smoke test on-demand to determine if any changes made to the business process doesn’t effect its core flow and functionality.

image

Using JDeveloper Studio

Business rules created as part of the project have a great means to be unit tested using the out of the box test features. A test suite XML can be created for a given decision function that may contain multiple test cases. Each test case is comprised of an input structure to the business rule and an expected output structure which is ascertained with the actual output of the rule.

Running the test from the studio also creates an inline test report with all the details about each test case and the overall test suite.

image

These approaches are good and provides a mean to ensure that a proper checks and balances are executed before deploying the project for any changes/incremental changes. However more rigorous quality checks can be implemented by automating the execution of these tests with each build and deployment cycle.

Test Automation

In order to industrialize the entire process of unit testing for it to be running with each deployment/release, it is essential that the test execution step be automated. It is highly desirable that with reach minor/major deployment, and ANT or a MAVEN task can be executed to test the business process and the business rules to produce a release specific test report. The composite test framework provides this capability out-of the box through a ANT file (ant-sca-test.xml in the MW_HOME/Oracle_SOA1/bin directory) and it is very convenient to use. However the problem with it is that it cannot test the business rules. This is a big limitation as far as a total test coverage is concerned.

However overcoming the limitation is easy and explained in the steps that follow. I managed to create a utility framework in Java using the business rules API’s and creating custom ANT tasks to test business rules, once they have been created by following the approach covered above and produce JUnit type reports. The composite test framework can also produce html reports with the test execution statues. However these reports can also be configured to be based on JUnit standards. Hence it makes sense to create all reports that are JUnit based particularly as tools such as Hudson can be configured to point to the report directories and automatically read test report files. It can do the rest in terms of embedding the test report per build/deployment.

  • Right click on the project in studio and create a new Ant build file from the project.
  • This generates two files (build.xml and build.properties) with all the class path and dependencies that the project is base on. This is a good starting point to modify and customize these files to add custom targets and properties execute both the composite and rule tests.

Ant

The customizations to the build.properties file are specific to the middleware installation folder. The property file also needs access to the BPM server to deploy and unit test the composites. A sample property file from my project is copied below.

Configuring build.properties

javac.debug=on

# Environment Homes
WL.HOME=C:/Oracle/PS5JDEV/wlserver_10.3
MW.HOME=C:/Oracle/PS5JDEV
JDEVELOPER.HOME=C:/Oracle/PS5JDEV/jdeveloper

# Output Directories
output.dir=SCA-INF/classes

# SCA Test Parameters
javac.deprecation=off
javac.nowarn=off
java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
java.naming.provider.url=t3://rubiconred-pc:7001/soa-infra
java.naming.security.principal=weblogic
java.naming.security.credentials=welcome123
dedicated.connection=true
dedicated.rmicontext=true
scatest.format=junit
scatest.result=./SCA-INF/out/scatests
scatest.timeout=60
scatest.partition=default

The entire build.xml file can be downloaded from here. However the two targets of interest to execute and report the composite tests are scatest and scareport. The scatest Ant target is executed by specifying a bunch of properties that are sourced from the build.properties file. An important parameter is the xsl attribute. This determines the format of the report that is generated. The default property is overwritten with the value of junit to create JUnit type reports.

Creating and Configuring the ANT build/property files

<property name="scatest.tasks.class.path" refid="classpath"/>
<taskdef name="scatest" classname="oracle.integration.platform.testfwk.ant.ScaTestTask" >
<classpath>
<pathelement path="${scatest.tasks.class.path}"/>
</classpath>
</taskdef>

<target name="scatest" description="Run the unit test cases in the Business Process" depends="compile,copy">
<echo message="Classpath = ${classpath}"/>
<echo message="Middleware Home = ${MW.HOME}"/>
<echo message="Running scatest using ORACLE HOME = ${JDEVELOPER.HOME} ${WL.HOME}"></echo>
<echo message="Using context = build.properties"></echo>
<scatest compositedn="${scatest.partition}/${scatest.input}" timeout="${scatest.timeout}"
xsl="${scatest.format}" runname="${scatest.runName}" resultsdir="${scatest.result}" context="${jndi.properties.input}">
</scatest>
</target>

<target name="scareport" description="Generate JUNIT type report for composite Test Suites" depends= "scatest">
<echo message="Generate report to ${scatest.result}"/>
<junitreport todir="${scatest.result}">
<fileset dir="${scatest.result}">
<include name="*.xml" />
</fileset>
<report format="frames" todir="${scatest.result}/html" />
</junitreport>
<exec executable="cmd.exe">
<arg line="/C start ${scatest.result}/html/index.html" />
</exec>
</target>

These targets can, in the most basic way be invoked by right clicking on the build file and selecting Run Ant Target > scareport. The wider possibility in the greater scheme of things is to execute this targets from a build control tool such as Hudson. Now, instead of Ant these targets can even be maven-(ized).

image

The composite test report is currently generated in the SCA-INF/out/scatests directory and automatically launched when running the scatest target. It shows the execution status of each test case and detailed failure report.

image

Integrating the Rule Test Framework

In order to execute the testing of business rules from a continuous integration standpoint, I have developed a Rule test framework which is wrapped over by an Ant command to test as well as generate JUnit based reports. The test framework is generic. A business rule can specify any number of test suite’s. The framework can read any number of test suite XMLs, unit test the business rule component against them and generate a report.The business rules test framework is a set of utility classes that in turn leverages the business rule APIs.

Creating Rule test and report targets

<target name="ruletest" description="Execute unit tests for business rules" depends="compile,copy">
<echo message="Using path = ${classpath}"></echo>
<java classname ="com.rxr.ruletest.RuleTestRunner" classpathref="classpath" logerror="on" failonerror="true" />
</target>

<target name="rulereport" description="Generate JUNIT type report for business Rule Tests">
<echo message="Generate report to ${ruletest.result}"/>
<junitreport todir="${ruletest.result}">
<fileset dir="${ruletest.result}">
<include name="*.xml" />
</fileset>
<report format="frames" todir="${ruletest.result}/html" />
</junitreport>
<exec executable="cmd.exe">
<arg line="/C start ${ruletest.result}/html/index.html" />
</exec>
</target>

The utility classes take care of report generation as well thus allowing robust testing that can be included as part of the build automation.

image

Conclusion

In the end it will be unusual to explain the benefit of establishing automation practices for every component being developed as part of a business process. My colleague, Craig Barr, recently pointed out in his recent blog about SOA and BPM industrialization, the importance of unit testing in a software delivery cycle. Craig mentions:

“Unit test everything! It is a myth that vendor tools don’t cut it when it comes to testing. You just need to know how!”

All files, resources and projects used in this blog can be downloaded from this location. As always I would love to have any feedback so please don’t hesitate provide any suggestions.

.

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.

Using Parametric Roles in Oracle BPM 11g

Folks having first hand experience in creating Business processes with our good old ALBPM would have certainly used parametric or dynamic role assignments in their processes.

Recently one of my colleague had asked me as how to do the same in Oracle BPM Suite 11g. I instantly remembered someone else also asking the same question in Oracle forums. Here are two such posts that discusses about the same

https://forums.oracle.com/forums/thread.jspa?threadID=1082890

https://forums.oracle.com/forums/thread.jspa?messageID=9351365

This article will demonstrate a very simple composite application with a BPM process that leverages the use of dynamic/parametric roles.

Creating the Process Skeleton

Create a SOA composite application in JDeveloper. Add BPM extension to it. Name the application as DynamicApprovalManagement. Drag a BPMN Process in the Components pane of the composite. Base it on a Manual Process Pattern.

Change the Start event from simple to a Message Start Event. Create a interface for the message start event based on the below schema.

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.soatechnologies.blog/card"
targetNamespace="http://www.soatechnologies.blog/card" elementFormDefault="qualified"></pre>
<xsd:element name="CreditInformation">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="cardNumber" type="xsd:string"/>
<xsd:element name="holderSsn" type="xsd:string"/>
<xsd:element name="holderFirstName" type="xsd:string"/>
<xsd:element name="holderLastName" type="xsd:string"/>
<xsd:element name="brand" type="xsd:string"/>
<xsd:element name="creditLimitRequested" type="xsd:double"/>
<xsd:element name="creditLimitGranted" type="xsd:double"/>
<xsd:element name="latePaymentCount" type="xsd:int"/>
<xsd:element name="creditScoringResult" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>

image

Create two Process Data Objects as creditInputRequest(based on the above XSD) and approvalOutcome of string type.

image

Next rename the UserTask with an appropriate name. Also double click on the user task and click on the Implementation tab. Implement the user task activity with a Human Task.

image

Implementing Dynamic/Rule based Participants in Human Task

Open the Business Process Navigator and browse the ApproveApplication.task that we just created inside DynamicApprovalManagement->Business Catalog->Human Tasks. Double click on the task to open the task editing wizard.

Click on the Assignment tab on the left. This will open the approval flow showing the task participants associated with it. Double click on the participant to choose a participant to execute this task.

Under Participant List choose Rule-based from the “Build a list of participant using :” option box.

Give any appropriate name in the List Ruleset field and click OK.

image

It would be interesting to see that this creates a Decision Service component based on Oracle Rules Engine with the name of the Ruleset that we just provided. There are some others as well but we can ignore them for the time being.

Click on the GetApprovers Ruleset (the name of the Ruleset that i choose. Depending upon what name you choose it can vary) and create an if then else type rule. Here as part of this demonstration I create a simple two rule Ruleset that evaluates credit limit requested for credit card application. If the credit limit is below 10k let a card approval clerk handle the approval/rejection. If it is more than 10k the approval is to be handled by a manager.

image

The dynamic participant or approval group is selected by calling the CreateResourceList function. The arguments used in this function are explained below

  • users – The name of any authenticated user in the server security realm.
  • groups – Instead of users we can also select a group in the server realm. The task will be assigned to all the users part of this group.
  • approles – This in important because while designing business process flows we dont care about users or groups but with application roles instead. They are equivalent to swimlane roles.
  • responseType – If the response type is REQUIRED, the assignee has to act on the task; otherwise, the assignment would be converted to an FYI assignment.
  • ruleName – Rule name is used to create an assignment reason. Rule set name + “_” + rule name is used as a key to look up the resource bundle for a translatable reason for assignment. This resource is looked up first in the project resource bundle, then in the custom resource bundle, and last in the system resource bundle
  • lists – This is an object that is a holder for all the lists that are built. Clicking this option shows a pre-asserted fact ‘Lists’ object to be used as the parameter.

For making this demonstration simpler i have chosen to select dynamic participants based on user names. Hence the reason why groups and approles are set to null values.

Go and create these two users in the Weblogic domain security realm.

image

Auto Generate a UI for the Human Task and compile the application.

If you now make or compile the application you will see that the build fails with the below exception

Error: BPM-71504: Unexpected error parsing ‘oramds:///soa/shared/workflow/TaskEvidenceService.xsd’.  Cause: oracle.mds.exception.MDSException: MDS-00054: The file to be loaded oramds:/soa/shared/workflow/TaskEvidenceService.xsd does not exist..  Action: Verify that file is valid and accessible

image

This is a known issue and took me sometime to find a workaround. Below is a note that i got in some Oracle document highlight the issue and its workaround. The below document explains this

http://www.art2dec.com/documentation/docs/fmw11g1114documentation/doc.1111/e15731.pdf

You Must Manually Remove Additional Slashes from the XSD file When Defining Human Tasks Assignments Based on Business Rules In BPM Suite if you define a human task assignments based on Business Rules, then
you must edit the XSD of the decision service to remove the additional slashes from the import statements.
For example, you must replace the following statement:

schemaLocation=”oramds:///soa/shared/workflow/TaskEvidenceService.xsd” with the following import statement: schemaLocation=”oramds:/soa/shared/workflow/TaskEvidenceService.xsd“.

For the example used in this article i had to open the xsd/DSApproveApplicationWorkflowTask.xsd and manually change the location.

image

Now build and deploy both the composite application and the UI project to a running SOA server.

Finally to see the composite project in action open the EM console and open the Test page for the application. Since we have created a BPM process with a Message Start Event it can be invoked as a webservice as well.

Test 1 : CreditLimtRequested < 10000

In this case ClerkApproval rule will fire and the task will land up in the inbox of clerk.

image

image

Test 2 : CreditLimtRequested > 10000

Similarly in this case ManagerApproval rule will fire and the task will land up in the inbox of manager.

image

image

Well needless to mention that this scenario is extremely useful in cases where the assignees have to be determined based on some business rules or even in cases where the information about workflow management is to be determined from the input payload itself.

The composite application and the UI Project can be downloaded from here. After downloading rename it to .zip and extract the JDeveloper application.

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=\"https://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

http://www.oracle.com/technetwork/articles/javase/index-140168.html

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 Decision Service and we require a thorough testing for them.

The entire rules application along with the Java Project created for the tutorial can be downloaded from here.