Question

What is the best practice to test drools rules with junit?

Until now we used junit with dbunit to test rules. We had sample data that was put to hsqldb. We had couple of rule packages and by the end of the project it is very hard to make a good test input to test certain rules and not fire others.

So the exact question is that how can I limit tests in junit to one or more certain rule(s) for testing?

Was it helpful?

Solution

Personally I use unit tests to test isolated rules. I don't think there is anything too wrong with it, as long as you don't fall into a false sense of security that your knowledge base is working because isolated rules are working. Testing the entire knowledge base is more important.

You can write the isolating tests with AgendaFilter and StatelessSession

StatelessSession session = ruleBase.newStatelessSesssion();

session.setAgendaFilter( new RuleNameMatches("<regexp to your rule name here>") );

List data = new ArrayList();
... // create your test data here (probably built from some external file)

StatelessSessionResult result == session.executeWithResults( data );

// check your results here.

Code source: http://blog.athico.com/2007/07/my-rules-dont-work-as-expected-what-can.html

OTHER TIPS

I created simple library that helps to write unit tests for Drools. One of the features is exactly what you need: declare particular drl files you want to use for your unit test:

@RunWith(DroolsJUnitRunner.class)
@DroolsFiles(value = "helloworld.drl", location = "/drl/")
public class AppTest {

    @DroolsSession
    StatefulSession session;

    @Test
    public void should_set_discount() {
        Purchase purchase = new Purchase(new Customer(17));

        session.insert(purchase);
        session.fireAllRules();

        assertTrue(purchase.getTicket().hasDiscount());
    }
}

For more details have a look on blog post: http://maciejwalkowiak.pl/blog/2013/11/24/jboss-drools-unit-testing-with-junit-drools/

Do not attempt to limit rule execution to a single rule for a test. Unlike OO classes, single rules are not independent of other rules, so it does not make sense to test a rule in isolation in the same way that you would test a single class using a unit test. In other words, to test a single rule, test that it has the right effect in combination with the other rules.

Instead, run tests with a small amount of data on all of your rules, i.e. with a minimal number of facts in the rule session, and test the results and perhaps that a particular rule was fired. The result is not actually that much different from what you have in mind, because a minimal set of test data might only activate one or two rules.

As for the sample data, I prefer to use static data and define minimal test data for each test. There are various ways of doing this, but programmatically creating fact objects in Java might be good enough.

A unit test with DBUnit doesn't really work. An integration test with DBUnit does. Here's why: - A unit test should be fast. -- A DBUnit database restore is slow. Takes 30 seconds easily. -- A real-world application has many not null columns. So data, isolated for a single feature, still easily uses half the tables of the database. - A unit test should be isolated. -- Restoring the dbunit database for every test to keep them isolated has drawbacks: --- Running all tests takes hours (especially as the application grows), so no one runs them, so they constantly break, so they are disabled, so there is no testing, so you application is full of bugs. --- Creating half a database for every unit test is a lot of creation work, a lot of maintenance work, can easily become invalid (with regards to validation which database schema's don't support, see Hibernate Validator) and ussually does a bad job of representing reality.

Instead, write integration tests with DBunit: - One DBunit, the same for all tests. Load it only once (even if you run 500 tests). -- Wrap each test in a transaction and rollback the database after every test. Most methods use propagation required anyway. Set the testdata dirty only (to reset it in the next test if there is a next test) only when propagation is requires_new. - Fill that database with corner cases. Don't add more common cases than are strictly needed to test your business rules, so ussually only 2 common cases (to be able to test "one to many"). - Write future-proof tests: -- Don't test the number of activated rules or the number of inserted facts. -- Instead, test if a certain inserted fact is present in the result. Filter the result on a certain property set to X (different from the common value of that property) and test the number of inserted facts with that property set to X.

Why not to choose same approach as we have for java code with unit and integration tests? Unit test is about taking minimum piece of code and test all possible usecases defining specification. With integration tests your goal is not all possible usecases but integration of several units that work together. Do the same with rules. Segregate rules by business meaning and purpose. Simplest 'unit under the test' could be file with single or high cohension set of rules and what is required for it to work (if any), like common dsl definition file and decision table. For integration test you could take meaningful subset or all rules of the system.

With this approach you'll have many isolated unit tests which will not be impacted and will not require support when you add new rules because you'll have isolated set of business rules with subset of input data to test respective business scenarios. And you'll have few integration tests with limited amount of common input data to reproduce and test 'common scenarios'. Adding new rules to integration test will require to update test output and will reflect how new rules impact common data flow.

Consider JUnit test rule that gives you possibility to load resources in declarative way and assert rules actually triggered. It will preserve inner assertion failures and report any rules triggering mismatch that hints you about what went wrong at a glance. Logging sheds light on cause-effect relationships between events. Works with SpringRunner.class for spring integration tests.

Example:

@DroolsSession(resources = {
        "classpath*:/org/droolsassert/rules.drl",
        "classpath*:/com/company/project/*/{regex:.*.(drl|dsl|xlsx|gdst)}",
        "classpath*:/com/company/project/*/ruleUnderTest.rdslr" },
        ignoreRules = { "before", "after" })
public class DroolsAssertTest {

    @Rule
    public DroolsAssert drools = new DroolsAssert();

    @Test
    @AssertRules("atomic int rule")
    public void testInt() {
        drools.insertAndFire(new AtomicInteger());
        assertEquals(1, drools.getObject(AtomicInteger.class).get());
    }
}

See rules.drl
More: https://github.com/droolsassert/droolsassert

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top