OptaPlanner is a great tool to solve planning problems, such as meeting scheduling, shift assignments, and more. However, these are often domains where people have grown to trust humans over computers. I've personally encountered a good bit of skepticism when I claim that a computer can solve scheduling problems better than a trained human. Whenever there may be doubt about the validity of a computer's solution, it's important to be able to prove that things are working properly.

Prior to OptaPlanner 7, this was a chore. Implementing solid testing for an individual rule required a decent amount of boilerplate, and working at a lower level of abstraction than ideal. With OptaPlanner 7, and the implementation of the ScoreVerifier interface, everything gets much simpler.

As an example, let's look at an OptaPlanner project for tutor scheduling, which you can find at this repository

In this example, Schedule is our PlanningSolution, and TutoringSession is our PlanningEntity. We're specifically hoping to test the rule "Students should only be scheduled for a single session", which stipulates that for any given Student, there should not be more than a single TutoringSession in the system. This planning rule is written using Drools DRL syntax.

rule "Students should only be scheduled for a single session"
    when
        TutoringSession($id: id, studentAssigned, $student: student, studentAssigned)
        TutoringSession(id > $id, $student == student)
    then
        scoreHolder.addHardConstraintMatch(kcontext, -1);
end

This rule is a prime example of the power of rules. We don't care about many other things in the system, including whether or not tutors are assigned, or any information about the specialties the tutors have or the students need. While these will be in other rules, this rule tests a small, independent piece. As a result, we expecting testing this to similarly enable us to be unconcerned with extra problem facts.

In most test frameworks, it's common to define a few helper objects that will be used throughout most tests. In this case, we are creating an IdCounter to give us unique, sequential IDs, and a ScoreVerifier to verify the score of a given rule. We pass the specific XML resource to the ScoreVerifier, so later on we can refer to rules by their natural DRL name.

HardSoftScoreVerifier<Schedule> scoreVerifier
IdCounter idCounter

def setup() {
    scoreVerifier = new HardSoftScoreVerifier<>(SolverFactory.createFromXmlResource("com/surrealanalysis/tutor/scheduling/solver/solverConfig.xml"))
    idCounter = new IdCounter()
}

Using Spock, our rule fits into a given, when, then formula, where given contains the problem facts, when contains the schedule we want to test, and then contains the score assertion.

def "Students should only be scheduled for a single session"() {
    given:
    def hour = new Hour(1)
    def table1 = new Table(idCounter.id)
    def table2 = new Table(idCounter.id)

    def student = new Student(idCounter.id, "Test Student 1", [], [], 1)
    def sessions = [new TutoringSession(id: idCounter.id, table: table1, hour: hour, student: student),
                    new TutoringSession(id: idCounter.id, table: table2, hour: hour, student: student)]
    when:
    Schedule schedule = new Schedule(
            tutors: [],
            students: [student],
            sessions: sessions,
            hours: [hour],
            specialties: [],
            tables: [table1, table2],
    )

    then:
    scoreVerifier.assertHardWeight("Students should only be scheduled for a single session", -1, schedule)
}

And there you have it. It's not particularly fancy, but it's quick to write, quick to run, and quick to assure stakeholders that things are working as expected.