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:

  1. Open your project in the UI
  2. Navigate to the Test tab
  3. Select or create test data
  4. Click Run to execute
  5. 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

  1. Run individual decision tables, not just Main
  2. Simplify test data to minimal reproduction
  3. 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