Testing Rules
Testing is essential for validating that your rules behave correctly. DTRules supports multiple testing approaches, from simple command-line validation to comprehensive test suites.
Why Test Rules?
- Verify correctness - Ensure rules produce expected results
- Catch regressions - Detect when changes break existing behavior
- Document behavior - Tests serve as executable documentation
- Build confidence - Deploy with assurance rules work correctly
Testing with the CLI
Validation
Check rules compile and are well-formed:
cd go
./dtrules -rules ../sampleprojects/CHIP/xml -validate List Tables
Verify decision tables are loaded:
./dtrules -rules ../sampleprojects/CHIP/xml -list Execute with Trace
Run rules and see execution details:
./dtrules -rules ../sampleprojects/CHIP/xml -entry Main -trace Testing with the Visual UI
The UI provides interactive testing:
- Open your project in the UI
- Navigate to the Test tab
- Select or create test data
- Click Run to execute
- Review results and trace output
Benefits of UI Testing
- Visual inspection of entity state before/after
- Step-through execution trace
- Easy modification of test data
- Quick iteration during development
Testing with Java
Basic Test Runner
public class TestRules {
public static void main(String[] args) throws Exception {
String path = System.getProperty("user.dir");
// Load rules
RulesDirectory rd = new RulesDirectory(path, "DTRules.xml");
RuleSet rs = rd.getRuleSet("MyRules");
// Create session
IRSession session = rs.newSession();
// Load test data
FileInputStream input = new FileInputStream("testfiles/test1.xml");
session.loadXml(input, "mapping");
input.close();
// Execute
session.execute("Main");
// Output results
session.printEntityReport(System.out, session.getState(), "results");
}
} JUnit Integration
public class RulesTest {
private RuleSet ruleSet;
@BeforeEach
void setup() throws Exception {
String path = System.getProperty("user.dir");
RulesDirectory rd = new RulesDirectory(path, "DTRules.xml");
ruleSet = rd.getRuleSet("MyRules");
}
@Test
void testEligibleCustomer() throws Exception {
IRSession session = ruleSet.newSession();
session.loadXml(new FileInputStream("testfiles/eligible.xml"), "mapping");
session.execute("Determine_Eligibility");
// Assert results
IREntity result = session.getState().find("application");
assertTrue((Boolean) result.get("eligible"));
}
@Test
void testIneligibleIncome() throws Exception {
IRSession session = ruleSet.newSession();
session.loadXml(new FileInputStream("testfiles/high_income.xml"), "mapping");
session.execute("Determine_Eligibility");
IREntity result = session.getState().find("application");
assertFalse((Boolean) result.get("eligible"));
assertEquals("income_too_high", result.get("reason_code"));
}
} Testing with Go
Go Test File
package rules_test
import (
"os"
"testing"
"github.com/dtrules/dtrules/go/pkg/dtrules/session"
)
func TestEligibility(t *testing.T) {
rs := session.NewRuleSet("TestRules")
eddFile, _ := os.Open("../xml/TestRules_edd.xml")
rs.LoadEDD(eddFile)
eddFile.Close()
dtFile, _ := os.Open("../xml/TestRules_dt.xml")
rs.LoadDecisionTables(dtFile)
dtFile.Close()
sess, _ := rs.NewSession()
rsess := sess.(*session.RSession)
// Set up test data
// ... load or create entities ...
// Execute
rsess.Execute("Main")
// Assert results
// ... verify entity state ...
} Test Case Design
Boundary Testing
Test values at decision boundaries:
// If threshold is 100:
test_at_99 // Just below - should fail
test_at_100 // At boundary - check which way it goes
test_at_101 // Just above - should pass Equivalence Classes
Group inputs that should behave the same:
// Age eligibility: 0-17 children, 18-64 adults, 65+ seniors
test_child (age = 10)
test_adult (age = 35)
test_senior (age = 70) BALANCED Table Coverage
For BALANCED tables, test each column:
// 3 conditions = 8 columns (2^3)
test_YYY // All conditions true
test_YYN // First two true, third false
test_YNY // First true, second false, third true
// ... all 8 combinations Edge Cases
- Empty arrays
- Null/missing values
- Zero amounts
- Negative numbers
- Maximum values
- Date boundaries (leap years, month ends)
Test Data Management
Test Data Files
Organize test data in the testfiles/ directory:
testfiles/
├── eligible/
│ ├── basic_eligible.xml
│ ├── with_dependents.xml
│ └── vip_customer.xml
├── ineligible/
│ ├── income_too_high.xml
│ ├── not_citizen.xml
│ └── age_invalid.xml
└── edge_cases/
├── empty_household.xml
├── zero_income.xml
└── boundary_values.xml Test Data Naming
Use descriptive names that indicate expected outcome:
test_eligible_basic.xml
test_ineligible_income_high.xml
test_edge_empty_dependents.xml
test_boundary_age_17.xml Regression Testing
Capture Expected Results
// For each test case, document expected output:
// test_eligible_basic.xml
// Expected:
// application.eligible = true
// application.benefit_amount = 250.00 Automated Comparison
// Run all tests and compare to expected
./run_tests.sh | diff - expected_output.txt Continuous Integration
Include rule tests in your CI pipeline:
# .github/workflows/test.yml
- name: Test Rules
run: |
cd go
go test ./...
./dtrules -rules ../sampleprojects/MyProject/xml -validate Debugging Failed Tests
Use Trace Output
./dtrules -rules path/to/rules -entry Main -trace 2>&1 | tee trace.log Check Intermediate Values
Add temporary logging in decision tables:
// In decision table action
set debug_income = household.total_income
set debug_threshold = poverty_level * 2 Isolate the Problem
- Run individual decision tables, not just Main
- Simplify test data to minimal reproduction
- Check entity state after each table execution
Best Practices
- Test as you develop - Write tests alongside rules
- Name tests clearly - The name should describe what's being tested
- One assertion per test - Makes failures easier to diagnose
- Test negative cases - Verify rules reject invalid inputs
- Keep tests independent - Each test should set up its own data
- Maintain test data - Update tests when rules change
Next Steps
- Operator Reference - Full operator documentation
- CHIP Tutorial - See tests in the sample project
- Deterministic Rules - Why testing works