Other Posts in this Series
Application Driven Testing of Drools with Fitnesse – part 1
Overview
In this post we will create a JUnit Test Case that fires a simple rule.
Step 1
First let’s create the classes and files we will need for our rule example:
- Person.java – this will be a fact that is inserted into the Rule Engine
- FileManager.java – contains utility methods
- RuleRunner.java – build the KnowledgeBase, and create StatefulKnowledgeSessions
- test1.drl – a simple rule
Create the following classes in their respective classes in src/main/java
Person.java
package com.skills421.model; public class Person { private String firstname; private String lastname; private int age; private double cmHeight; private double kgWeight; public Person() { } public Person(String firstname, String lastname, int age, double cmHeight, double kgWeight) { super(); this.firstname = firstname; this.lastname = lastname; this.age = age; this.cmHeight = cmHeight; this.kgWeight = kgWeight; } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() { return lastname; } public void setLastname(String lastname) { this.lastname = lastname; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getCmHeight() { return cmHeight; } public void setCmHeight(double cmHeight) { this.cmHeight = cmHeight; } public double getKgWeight() { return kgWeight; } public void setKgWeight(double kgWeight) { this.kgWeight = kgWeight; } @Override public String toString() { return String.format("Person [firstname=%s, lastname=%s, age=%s, cmHeight=%s, kgWeight=%s]", firstname, lastname, age, cmHeight, kgWeight); } }
FileManager.java
package com.skills421.managers; import java.io.File; import java.io.IOException; public class FileManager { public static String replaceAllDotsWithPathSeparator(String packagename) { // if we are on a unix box, replace all .'s in the package name with /'s // if we are on a windows box replace all .'s in the package name with // \'s if (File.separator.equals("/")) { packagename = packagename.replaceAll("\\.", "/"); } else { packagename = packagename.replaceAll("\\.", "\\\\"); } return packagename; } public static String convertRulePackageAndFileToFullPathname(String packagename, String filename) { // turn packagename to file path StringBuilder fullpathnameSB = new StringBuilder(); fullpathnameSB.append(FileManager.replaceAllDotsWithPathSeparator(packagename)) .append(File.separator) .append(filename); return fullpathnameSB.toString(); } public static String convertRulePackageAndFileToFullPathname(String packageAndFilename) throws IOException { // in the event that we get passed a filepath that actually exists // just return it if (new File(packageAndFilename).exists()) return packageAndFilename; // remove the extension if(packageAndFilename.indexOf(".")==-1) { throw new IOException("No file extension provided for "+packageAndFilename); } int extPstn = packageAndFilename.lastIndexOf("."); String packagename = packageAndFilename.substring(0,extPstn); String ext = packageAndFilename.substring(extPstn); StringBuilder fullpathnameSB = new StringBuilder(); fullpathnameSB.append(FileManager.replaceAllDotsWithPathSeparator(packagename)) .append(ext); return fullpathnameSB.toString(); } }
RuleRunner.java
package com.skills421.rules; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.drools.KnowledgeBase; import org.drools.KnowledgeBaseFactory; import org.drools.builder.KnowledgeBuilder; import org.drools.builder.KnowledgeBuilderError; import org.drools.builder.KnowledgeBuilderFactory; import org.drools.builder.ResourceType; import org.drools.decisiontable.InputType; import org.drools.decisiontable.SpreadsheetCompiler; import org.drools.definition.KnowledgePackage; import org.drools.io.Resource; import org.drools.io.ResourceFactory; import org.drools.runtime.StatefulKnowledgeSession; import com.skills421.managers.FileManager; public class RuleRunner { private static Logger logger = LogManager.getLogger(RuleRunner.class); private KnowledgeBase kbase; private KnowledgeBuilder kbuilder; public RuleRunner() { kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); } public List<String> buildKnowledgeBase(String[] decisionTables, String[] drlFiles) { List<String> buildErrors = new ArrayList<String>(); if (decisionTables != null) { logger.info("*** Adding Decision Tables ***"); for (int i = 0; i < decisionTables.length; i++) { String decisionTable = decisionTables[i]; Resource xlsResource = ResourceFactory.newClassPathResource(decisionTable, RuleRunner.class); kbuilder.add(xlsResource, ResourceType.DTABLE); } } if (drlFiles != null) { logger.info("*** Adding DRL Files ***"); for (int i = 0; i < drlFiles.length; i++) { try { String drlFile = FileManager.convertRulePackageAndFileToFullPathname(drlFiles[i]); if (drlFile.startsWith(File.separator) || drlFile.toUpperCase().startsWith("C:")) { kbuilder.add(ResourceFactory.newFileResource(new File(drlFile)), ResourceType.DRL); } else { kbuilder.add(ResourceFactory.newClassPathResource(drlFile, RuleRunner.class), ResourceType.DRL); } } catch (IOException e) { buildErrors.add(e.getMessage()); } } } logger.info("*** Checking for Errors ***"); if (kbuilder.hasErrors()) { logger.info("*** !!! Build Errors !!! ***"); for (KnowledgeBuilderError error : kbuilder.getErrors()) { logger.info(error); buildErrors.add(error.toString()); } } logger.info("*** Loading KnowledgePackages ***"); Collection<KnowledgePackage> pkgs = kbuilder.getKnowledgePackages(); kbase.addKnowledgePackages(pkgs); return buildErrors; } public List<String> buildKnowledgeBase(List<String> decisionTables, List<String> rules) { String[] decisionTablesArr = null; String[] rulesArr = null; if (decisionTables != null) decisionTablesArr = decisionTables.toArray(new String[decisionTables.size()]); if (rules != null) rulesArr = rules.toArray(new String[rules.size()]); return this.buildKnowledgeBase(decisionTablesArr, rulesArr); } public static String getDRLFromDecisionTable(String decisionTable) { String drlContent = null; try { SpreadsheetCompiler sc = new SpreadsheetCompiler(); Resource xlsResource = ResourceFactory.newClassPathResource(decisionTable, RuleRunner.class); drlContent = sc.compile(xlsResource.getInputStream(), InputType.XLS); } catch (Exception e) { e.printStackTrace(); } return drlContent; } public StatefulKnowledgeSession newKnowledgeSession() { return kbase.newStatefulKnowledgeSession(); } public static void logDrlContent(String decisionTable) throws IOException { String drlContent = RuleRunner.getDRLFromDecisionTable(decisionTable); logger.info("DRLContent: " + drlContent); } }
test1.drl
in src/test/resources create test1.drl in the sub-folder com/skills421/rules
package com.skills421.rules import com.skills421.model.Person import java.util.List dialect "mvel" global List OUTPUTLIST /* * This rule checks that a person is >= 21 */ rule "Person is 21" when $person : Person(age>=21) then OUTPUTLIST.add(String.format("%s %s",$person.firstname,$person.lastname)); end
Testing FileManager
FileManager provides 3 static methods that are used to determine the file location of a drl file from the package name and filename.
our test1.drl is in the package com.skills421.model, so the actual file should be com/skills421/rules/test1.drl somewhere on the classpath.
The three static methods in FileManager are as follows:
- replaceAllDotsWithPathSeparator
this will take a String of the format com.skills421.rules and convert it to com/skills421/rules for a Unix platform and \com\skills421\rules for a Windows platform. - convertRulePackageAndFileToFullPathname(packagename,filename)
this will take a package name – e.g. com.skills421.rules and a filename – e.g. test1.drl and produce a full path of com/skills421/rules/test1.drl - convertRulePackageAndFileToFullPathname(packageAndFile)
this will take a package name – e.g. com.skills421.rules.test1.drl and produce a full path of com/skills421/rules/test1.drl
Create the file FileManagerTest.java in the appropriate package in src/test/java
FileManagerTest.java
package com.skills421.managers; import static org.junit.Assert.*; import java.io.IOException; import org.junit.Test; public class FileManagerTest { @Test public void testReplaceAllDotsWithPathSeparator() { String packagename = "com.skills421.rules"; String path = FileManager.replaceAllDotsWithPathSeparator(packagename); assertEquals("com/skills421/rules", path); } @Test public void testConvertPackageNameToDrlFilePathOneArg() { String packagename = "com.skills421.rules.test1.drl"; try { String drlFilePath = FileManager.convertRulePackageAndFileToFullPathname(packagename); assertEquals("com/skills421/rules/test1.drl", drlFilePath); } catch (IOException e) { fail(e.getMessage()); } } @Test public void testConvertPackageNameToDrlFilePathTwoArgs() { String packagename = "com.skills421.rules"; String filename = "test1.drl"; String drlFilePath = FileManager.convertRulePackageAndFileToFullPathname(packagename, filename); assertEquals("com/skills421/rules/test1.drl", drlFilePath); } }
Now right click on the file and run it as a JUnit test. You should see a green bar indiciating that all the tests ran successfully.
Testing RuleRunner
RuleRunner provides a number of methods of which we are currently interested in the following:
- buildKnowledgeBase
- newKnowledgeSession
We need to write a couple of JUnit tests to firstly build the KnowledgeBase from test1.drl, and secondly run the rules defined in test1.drl.
Create the file RuleRunnerTest.java in the appropriate package in src/test/java
RuleRunnerTest.java
package com.skills421.rules; import static org.junit.Assert.*; import java.util.ArrayList; import java.util.List; import org.drools.runtime.StatefulKnowledgeSession; import org.junit.Before; import org.junit.Test; import com.skills421.model.Person; public class RuleRunnerTest { private RuleRunner ruleRunner; @Before public void setupRunner() { ruleRunner = new RuleRunner(); } @Test public void testBuildKnowledgeBase() { String[] decisionTables = null; String[] drlFiles = new String[] { "com.skills421.rules.test1.drl" }; List<String> errors = ruleRunner.buildKnowledgeBase(decisionTables, drlFiles); assertTrue(errors.size()==0); } @Test public void testRunRules() { String[] decisionTables = null; String[] drlFiles = new String[] { "com.skills421.rules.test1.drl" }; List<String> errors = ruleRunner.buildKnowledgeBase(decisionTables, drlFiles); assertTrue(errors.size()==0); StatefulKnowledgeSession ksession = ruleRunner.newKnowledgeSession(); Person p1 = new Person("Jon","Doe",18,1.8,80); Person p2 = new Person("Jane","Doe",22,1.9,70); List<String> outputList = new ArrayList<String>(); ksession.setGlobal("OUTPUTLIST", outputList); ksession.insert(p1); ksession.insert(p2); ksession.fireAllRules(); assertEquals(1, outputList.size()); assertEquals("Jane Doe", outputList.get(0)); } }
The first test simply passes test1.drl to an instance of RuleRunner and builds the KnowledgeBase, checking that there are no errors.
The second test is a little more involved and obtains a StatefulKnowledgeSession from the KnowledgeBase. It first inserts a Global List into the Session which is used for retrieving a list of String output from the Rule Engine. Next, it inserts 2 Person objects (facts), and finally it fires the rules.
With our rule as specified in test1.drl we are expecting one Person to satisfy our rules, namely “Jane Doe”. The rule, on finding a match will insert the full name of the matched person into the outputList.
So, once our rules have fired, we can check our outputList for 1 match – “Jane Doe”.
Right click on RuleRunnerTest and run as Junit Test.
You should see a green bar indicating that the JUnit tests ran successfully.
Download the Source
The source for part-2 can be downloaded form GitHub here.
[…] Application Driven Testing of Drools with Fitnesse – part 2 […]
[…] Application Driven Testing of Drools with Fitnesse – part 2 […]