NOTE: For latest update on CICD of BusinessEvents with Maven, jUnit and jacoco, see the newer project be_sample
This tutorial is a step-by-step guide for unit test of TIBCO BusinessEvents (BE) applications. It builds 2 projects from scratch to demonstrate steps for adding unit tests to a BusinessEvents sample project:
- FraudDetectionCache, which is a sample from BusinessEvents 5.2. This tutorial will add unit tests for the rules and rule functions to this project.
- FraudDetectionTest, which is a Maven project for driving the unit tests using JUnit.
You may follow this tutorial to build the 2 projects from scratch, or you may skip to the last section to configure and execute the pre-built projects of this tutorial.
The following 3 projects contain source code and instructions for the test utilities used by this tutorial:
- DataTypeDemo, which contains more samples of unit tests and assertions for BusinessEvents applications. The project library,
testservice.projlib
, is extracted from this project. - BE Assert, which contains source code of assertion functions for BE unit tests. The catalog function jar
beassert-1.0.jar
is packaged from this project. - BE Unit, which contains utility functions to drive the unit tests using JUnit. The test driver,
FraudDetectionTest
is based on this project.
As business agility has put more and more pressure on IT organization to deliver application updates continuously, automated unit testing has become a crucial requirement for all applications. Rule-based systems like TIBCO BusinessEvents are no exception. Even though it is challenging to test individual rules and functions in isolation, developers must be able to write unit tests to assess the correctness of individual rules and functions due to the following reasons:
- Unit test provides more confidence to developers when they update or add functionality to applications, and thus a full set of unit tests encourages code refactoring.
- Unit test improves the code coverage, especially for rule-based systems, it is harder to improve code coverage by adding functional tests.
- Unit test helps documenting the code, and improves the code quality by ensuring the testability of the code.
This tutorial shows that unit test of individual rules and rule-functions is not only necessary, but also achievable for TIBCO BusinessEvents applications.
This tutorial requires installation of Git and Maven. The project BE Unit describes steps for setting up the development environment.
Clone the tutorial code and utilities from GitHub using the following command:
git clone https://github.com/yxuco/betest_tutorial.git
This will download the tutorial to a folder betest_tutorial
. It contains 2 projects, FraudDetectionCache
and FraudDetectionTest
, which will be rebuilt from scratch in the following sections. The lib
folder contains jars and BE project library used by this tutorial. If you are curious, you may build the jar and project library from source code in BE Assert and DataTypeDemo.
In this section, we import the BE sample, FraudDetectionCache
, and configure it for unit testing.
- Launch BusinessEvents Studio, pull down menu File -> Import…
- In Import dialog, select Existing TIBCO BusinessEvents Studio Project, click Next >
- Browse
Existing project root directory
, and select$BE_HOME/examples/standard/FraudDetectionCache/FraudDetectionCache
- Check
Copy project into workspace
if it is not already checked, click Finish - In Studio Explorer, right click root of the imported project FraudDetectionCache, in popup menu, select Build Enterprise Archive…
- Browse
File Location
, and specifyfdcache.ear
under the folderbetest-tutorial
. Do not worry about overriding theear
file from GitHub.
The project should build successfully, and generate an ear
file.
- In Studio Explorer, right click root of the project FraudDetectionCache, in popup menu, select Properties
- Select Build Path, in Project Libraries panel, click Add Library…
- Select file
betest_tutorial/lib/testservice.projlib
from the downloaded Git repository, Click Open - Click Java Libraries tab, and then Add Library…, select and add file
betest_tutorial/lib/beassert-1.0.jar
as a Third Party library - Click OK to exit the build path configuration
These steps added the test-service component and the assert catalog functions to the project. It uses 2 Global Variables that we need to define next.
- Highlight the project root FraudDetectionCache in the Studio Explorer, then pull down menu Project -> Edit Global Variables
- Add Group TestService
- Under TestService group, add variable Host = localhost
- Under TestService group, add variable Port = 8989
This port will be used by the test driver FraudDetectionTest
to send test requests via HTTP.
Open fdcache.cdd
, modify the runtime configuration as follows
- In Agent Classes panel, highlight Inference-class (Inference), turn on Concurrent RTC
- Open folder Input Destination Collections, and add the following 2 destinations
- TestFunction_01, URI=
/testservice/test/function
, Preprocessor=/testservice/RuleFunctions/processFunctionRequest
- TestTrigger_01, URI=
/testservice/TestScheduler/TestTriggerChannel/PreprocTestTrigger
, Preprocessor=/testservice/TestSchedule/processPreprocTestTrigger
- In Processing Units panel, highlight default PU, turn on Enable Cache Storage (This is convenient for development although not recommended for production.)
This completes all necessary configurations. You can rebuild the ear
to make sure that the configuration did not introduce any error.
Our goal is to write unit tests for all rules and rule functions in a BE application. However, compared to functional or object oriented programming, rule-based program is the hardest to unit test.
- For functions, the best testable functions are pure functions that do not have any side effects, but rule-based systems are mostly based on side effects, and so most rule functions are not pure functions, and thus harder to write unit tests.
- For rules, they are not only based on side effects by updating objects in the working memory, but also they can execute in nondeterministic sequences.
Besides, BusinessEvents applications have a few more properties that must be considered carefully in unit tests.
- Concepts and Events are 2 different types of data structures. Concepts are typically mutable data that are persisted and shared globally across multiple threads and JVMs. Events are usually immutable data that are transient and not shared by multiple threads, although they can be sent between different threads.
- Rule-functions can run in 2 different contexts, i.e., preprocessor or rule context. A rule-function for preprocessor context behaves much like a pure function because it cannot update Concepts/Scorecards to create side-effects, although it can update other shared data structures, i.e., in-memory mutable collections (List, Set, or Map).
- Rules can also be executed in the 2 different contexts. However, when Cache-Only and Concurrent-RTC are used, we can use preprocessor context to isolate the test of rules.
In this tutorial, all tests are executed in preprocessor context. Examples for rule context and more BE data types can be found in DataTypeDemo.
As a covention, we add all unit test functions under a separate folder /Test
. We explain only one of the tests for a rule ApplyDebit
, whose action is to reduce the balance of a matching account by a debit amount. The test is implemented as a rule-function:
void rulefunction Test.RuleTests.testApplyDebit {
attribute {
validity = ACTION;
}
scope {
}
body {
Object logger = Log.getLogger("Test.RuleTests.testApplyDebit");
Log.log(logger, "info", "Start Test.RuleTests.testApplyDebit");
String acctId = "ApplyDebit";
cleanupAccount(acctId);
// create account for the test
Concepts.Account.Account(
acctId /*extId String */,
20000 /*Balance double */,
0 /*Debits double */,
"Normal" /*Status String */,
2000 /*AvgMonthlyBalance double */);
Engine.executeRules();
// load account into working memory
Account acct = Cluster.DataGrid.CacheLoadConceptByExtId(acctId, true);
Debit evt = Events.Debit.Debit(
null /*extId String */,
null /*payload String */,
acctId /*AccountId String */,
2000 /*Amount double */);
// execute all rules for debit event, but only the ApplyDebit rule will be triggered
Event.assertEvent(evt);
Engine.executeRules();
// reload updated account to verify the update
acct = Cluster.DataGrid.CacheLoadConceptByExtId(acctId, true);
assertNotNull(String.format("Account %s exists", acctId), acct);
assertWithinRange("Account balance is updated", 20000 - 2000, acct.Balance, 0.001);
assertWithinRange("Account debit is recorded", 2000, acct.Debits, 0.001);
assertThat("Account status is set", acct.Status, equalTo("Normal"));
Log.log(logger, "info", "Completed Test.RuleTests.testApplyDebit");
}
}
This test implements a common pattern for verifying rules in preprocessor context.
- Test function uses no input parameter (i.e.,
scope
), and returns nothing (i.e.,void
). cleanAccount()
deletes any harmful objects from the cache, so the same test can run multiple times with the same result.- It then creates a new account for the test. The first call of
Engine.executeRules()
commits the new account to the cache. - It then loads the new account into working memory using
CacheLoadConceptByExtId()
. - It then creates and asserts a
Debit
event into working memory, and fires theApplyDebit
rule using a second call ofEngine.executeRules()
. - It then reloads the updated account from cache by calling
CacheLoadConceptByExtId()
again. - 4 assert statements verify that the account is correctly updated by the
ApplyDebit
rule. - A runtime exception would be thrown if any assert statement fails; the exception would be caught and counted by the test service.
More advanced assertion are supported by the catalog functions in beassert-1.0-jar
. Refer to the unit tests in DataTypeDemo for more examples.
During development, the easiest way to launch unit tests is at BE engine startup. However, tests for rules can only be launched after the engine is fully started. So, the test service implemented a scheduler to launch rule tests 3 seconds after the engine startup.
2 helper functions are used to launch unit tests at engine startup:
allStartupTests
, which lists all tests to be executed during engine startup. You can add test names to this function for tests that can be launched during startup, which includes tests of functions in preprocessor context.allScheduledTests
, which lists all tests to be scheduled after the engine startup. You can add test names to this function for tests that cannot be launched during startup, which includes tests for rules and functions in rule context.
Open fdcache.cdd
, and add these 2 functions to Startup Functions of the Inference-class in the Agent Classes tab.
Rebuild fdcache.ear
.
Highlight project root FraudDetectionCache, pull down menu Run -> Run Configurations…. Set a configuration for BusinessEvents Application
with the following parameters:
- CDD File: full path to
fdcache.cdd
in the project workspace - Processing Unit:
default
- Working Directory: the
betest_tutorial
directory where you putfdcache.ear
- EAR File: full path of
fdcache.ear
- ClassPath: Open the ClassPath tab, add
junit-4.12.jar
andhamcrest-core-1.3.jar
to the Third Party library. You can find these 2 jars from your local Maven repository, or from the downloadedbetest_tutorial/lib
folder.
This launches the BE engine in BusinessEvents Studio. Test completed messages should show up in the Console log.
Although it is quick to launch unit tests at engine startup during development, it is not convenient to scan log files for failed tests, nor is it good to integrate with CI tools such as Jenkins.
This section will build a test driver based on JUnit, and so you can visualize the test results in Eclipse, or Jenkins, or any other tools that are integrated with JUnit.
This test driver is implemented in BE Unit, which may be already in your workspace if you have tried the beunit project previously. We’ll create a copy of this project to explain the process.
In your workspace, clone the beunit project and name it FraudDetectionTest
using the command:
git clone https://github.com/yxuco/beunit.git FraudDetectionTest
In BusinessEvents Studio, import and configure the project as follows:
- Pull down menu File -> Import…, and select Existing Maven Projects, Click Next >
- Browse Root Directory and select the cloned project
FraudDetectionTest
- Open the Advanced section, and specify Name template: as
FraudDetectionTest
, click Finish - Select the FraudDetectionTest project, close all files, and pull down menu Window -> Open Perspective to open Java Perspective.
- In the
src/test/java
package folder, delete the JUnit test package and classes from the original beunit project - Add new JUnit test package and class, e.g.,
frauddetection.RuleTest
, as follows:
package frauddetection;
import org.junit.Test;
import com.tibco.psg.beunit.TestHelper;
public class RuleTest {
public static final String folder = "/Test/RuleTests/";
@Test
public void testApplyDebit() {
TestHelper.assertRuleFunction(folder + "testApplyDebit", true);
}
@Test
public void testBadApplyDebit() {
TestHelper.assertRuleFunction(folder + "testBadApplyDebit", true);
}
@Test
public void testBadCreateAccount() {
TestHelper.assertRuleFunction(folder + "testBadCreateAccount", true);
}
@Test
public void testCheckNegativeBalance() {
TestHelper.assertRuleFunction(folder + "testCheckNegativeBalance", true);
}
@Test
public void testCreateAccount() {
TestHelper.assertRuleFunction(folder + "testCreateAccount", true);
}
@Test
public void testUnsuspendAccount() {
TestHelper.assertRuleFunction(folder + "testUnsuspendAccount", true);
}
}
This JUnit class simply enumerates the full path of all unit tests in the FraudDetectionCache
project, and thus it may be automatically generated by a code generation tool.
The test driver sends test requests to the FraudDetectionCache
engine via HTTP port 8989. To execute the tests,
-
First, launch
FraudDetectionCache
in BusinessEvents Studio as described in the previous section; -
Then, from command shell, change to the project root folder of
FraudDetectionTest
, and execute the commandmvn test
This should print out BUILD SUCESS
if all unit tests complete without failures.
We can also reverse the process, and run FraudDetectionCache
from a command shell, and run the test driver FraudDetectionTest
from the BusinessEvents Studio, so we can visualize the test results.
The downloaded folder betest_tutorial
contains a file be-engine.tra
which is based on the BE default tra
file but added 3 jar files to the classpath, i.e., beassert-1.0.jar
, junit-4.12.jar
, and hamcrest-core-1.3.jar
, which are required to run FraudDetectionCache
. So, edit this file to set CUST_LIB to match your directory structure.
The downloaded folder betest_tutorial
also contains a script run_fdcache
for launching the BE engine from command shell. Edit the the variables in this script to match your directory structure, and then use it to start the BE engine from command prompt.
You can then start JUnit tests from BusinessEvents Studio as follows:
- Open Java Perspective
- In Package Explorer, right click the package
src/test/java
in FraudDetectionTest, and from the popup menu, select Run As -> JUnit Test
You should see the familiar JUnit green bar in the BusinessEvents Studio and a list of completed tests.
If you did not go through the steps of the above sections, you can import the 2 downloaded projects into BusinessEvents Studio, and see how the unit tests work. Refer to the the previous sections for more details about these steps.
- Import
FraudDetectionCache
as an Existing TIBCO BusinessEvents Studio Project - Import
FraudDetectionTest
as an Existing Maven Project - In
FraudDetectionCache
project, set Build Path to includetestservice.projlib
andbeassert-1.0.jar
from the downloadedbetest_tutorial/lib
folder. - When launching
FraudDetectionCache
from BusinessEvents Studio, make sure thatjunit-4.12.jar
andhamcrest-core-1.3.jar
are added to the Classpath. - When lauching
FraudDetectionCache
from command shell, edit the variableCUST_LIB
inbe-engine.tra
and the environment variables in the scriptrun_fdcache
to match your directory structure. - You may either run BE engine from command shell and JUnit in BusinessEvents Studio, or run BE engien in BusinessEvents Studio, and JUnit from command shell. Refer to the previous sections for more details.
Yueming is a Sr. Architect working at TIBCO Architecture Service Group.