2010-10-05 13 views
9

Qual è la migliore pratica per testare le regole di drools con junit?Prova di drools con junit

Fino ad ora abbiamo usato junit con dbunit per testare le regole. Abbiamo dati di esempio che sono stati inseriti in hsqldb. Avevamo due pacchetti di regole e alla fine del progetto è molto difficile fare un buon test per testare certe regole e non licenziare altre.

Quindi la domanda esatta è che come posso limitare i test in junit a una o più regole certe per il test?

Grazie per l'aiuto,

Hubidubi

risposta

6

Personalmente utilizzo test di unità per testare regole isolate.Non penso che ci sia qualcosa di sbagliato in questo, fintanto che non cadi in un falso senso di sicurezza che la tua base di conoscenze funziona perché le regole isolate funzionano. Testare l'intera knowledge base è più importante.

È possibile scrivere i test di isolamento con AgendaFilter e 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. 

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

4

Non tentare di limitare l'esecuzione regola a una singola regola per un test. A differenza delle classi OO, le singole regole non sono indipendenti da altre regole, quindi non ha senso testare una regola in isolamento nello stesso modo in cui si testerebbe una singola classe usando un test unitario. In altre parole, per testare una singola regola, verifica che abbia l'effetto giusto in combinazione con le altre regole.

Invece, esegui test con una piccola quantità di dati su tutte le tue regole, cioè con un numero minimo di fatti nella sessione di regole, e verifica i risultati e forse è stata attivata una particolare regola. Il risultato non è in realtà molto diverso da quello che hai in mente, perché un set minimo di dati di test potrebbe attivare solo una o due regole.

Come per i dati di esempio, preferisco utilizzare i dati statici e definire i dati di test minimi per ciascun test. Esistono vari modi per farlo, ma la creazione di oggetti di fatto in Java a livello di codice potrebbe essere abbastanza buona.

+0

Sì, lo so come funziona l'esecuzione regola. Questo è il modo in cui lo facciamo ora. Il mio problema è con questo approccio che è molto difficile creare dati di test sufficienti e appropriati. Poiché non limitiamo le regole eseguibili, è possibile eseguire qualsiasi altra regola e modificare il risultato finale. Quindi è difficile prevedere il risultato finale per le asserzioni. Questa era la ragione per cui pensavo che sarebbe stato meglio testare le regole izolated. – Hubidubi

+0

Suppongo che stavo cercando di dire che il fatto che "qualsiasi altra regola può essere eseguita e modificare il risultato finale" è esattamente il motivo per cui testare una regola in modo isolato è meno significativo. –

4

Un test di unità con DBUnit non funziona veramente. Lo fa un test di integrazione con DBUnit. Ecco perché: - Un test di unità dovrebbe essere veloce. - Un ripristino del database DBUnit è lento. Prende facilmente 30 secondi. - Un'applicazione reale ha molte colonne non nulle. Quindi i dati, isolati per una singola funzione, utilizzano ancora facilmente metà delle tabelle del database. - Un test unitario deve essere isolato. - Il ripristino del database dbunit per ogni test per tenerli isolati presenta degli svantaggi: --- L'esecuzione di tutti i test richiede ore (specialmente con l'aumentare dell'applicazione), quindi nessuno li esegue, quindi si interrompono costantemente, quindi sono disabilitati, quindi non ci sono test, quindi l'applicazione è piena di bug. --- Creazione di un mezzo database per ogni unità di test è un sacco di lavoro di creazione, un sacco di lavori di manutenzione, può facilmente diventare non valido (per quanto riguarda la validazione che lo schema del database non supporta, vedere Hibernate Validator) e usualmente fa un cattivo lavoro di rappresentare la realtà.

Invece, scrivere test di integrazione con DBUnit: - Una DBUnit, lo stesso per tutti i test. Caricalo solo una volta (anche se esegui 500 test). - Completa ogni test in una transazione e ripristina il database dopo ogni test. La maggior parte dei metodi utilizza comunque la propagazione richiesta. Imposta solo il dirty test (per resettarlo nel prossimo test se c'è un test successivo) solo quando la propagazione è require_new. - Compila quel database con casi d'angolo. Non aggiungere più casi comuni di quelli strettamente necessari per testare le regole aziendali, quindi di solito solo 2 casi comuni (per poter testare "uno a molti"). - Scrivi test a prova di futuro: - Non testare il numero di regole attivate o il numero di fatti inseriti. - Verificare invece se un determinato dato inserito è presente nel risultato. Filtra il risultato su una certa proprietà impostata su X (diverso dal valore comune di quella proprietà) e verifica il numero di fatti inseriti con quella proprietà impostata su X.

4

ho creato semplice libreria che aiuta a scrivere unit test per Drools. Una delle caratteristiche è esattamente quello che vi serve: dichiarare particolari file DRL che si desidera utilizzare per il vostro test di unità:

@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()); 
    } 
} 

Per maggiori dettagli date un'occhiata sul post del blog: http://maciejwalkowiak.pl/blog/2013/11/24/jboss-drools-unit-testing-with-junit-drools/

+0

Il link non è valido: "Server non trovato" – snorbi

0

A volte non si può controllare cambiato stato dei tuoi fatti, a causa della natura delle regole di drools, ad esempio possono chiamare percorsi cammello. Con il seguente apprach puoi verificare se la regola è stata licenziata, quante volte e quando. Puoi affermare tutte le regole attivate dopo l'inserimento di alcuni fatti e affermare che non sono state emesse regole indesiderate. L'approccio è basato sull'implementazione di AgendaEventListener.

public class DroolsAssertTest { 
    private static DroolsAssert droolsAssert; 

    @Before 
    public void before() { 
     droolsAssert = new DroolsAssert(DroolsAssertTest.class, "rules.drl"); 
    } 

    @After 
    public void after() { 
     droolsAssert.dispose(); 
    } 

    @Test 
    public void testDummyBusinessLogic() { 
     droolsAssert.insertAndFire(...); 
     droolsAssert.awaitForActivations("some rule has been activated"); 
    } 
... 

import static java.lang.String.format; 
import static java.lang.System.out; 
import static java.util.Collections.sort; 
import static java.util.concurrent.TimeUnit.MILLISECONDS; 
import static org.junit.Assert.assertEquals; 
import static org.junit.Assert.assertFalse; 
import static org.junit.Assert.assertTrue; 
import static org.junit.Assert.fail; 

import com.google.common.base.Equivalence; 
import com.google.common.collect.Collections2; 
import com.google.common.collect.ImmutableMap; 
import org.drools.core.common.DefaultAgenda; 
import org.drools.core.event.DefaultAgendaEventListener; 
import org.drools.core.event.DefaultRuleRuntimeEventListener; 
import org.drools.core.time.SessionPseudoClock; 
import org.kie.api.command.Command; 
import org.kie.api.event.rule.BeforeMatchFiredEvent; 
import org.kie.api.event.rule.ObjectDeletedEvent; 
import org.kie.api.event.rule.ObjectInsertedEvent; 
import org.kie.api.event.rule.ObjectUpdatedEvent; 
import org.kie.api.io.ResourceType; 
import org.kie.api.runtime.KieSessionConfiguration; 
import org.kie.api.runtime.rule.FactHandle; 
import org.kie.internal.KnowledgeBase; 
import org.kie.internal.KnowledgeBaseFactory; 
import org.kie.internal.builder.KnowledgeBuilder; 
import org.kie.internal.builder.KnowledgeBuilderFactory; 
import org.kie.internal.io.ResourceFactory; 
import org.kie.internal.runtime.StatefulKnowledgeSession; 

import java.util.Collection; 
import java.util.Comparator; 
import java.util.HashMap; 
import java.util.IdentityHashMap; 
import java.util.LinkedList; 
import java.util.List; 
import java.util.Map; 
import java.util.concurrent.ConcurrentHashMap; 
import java.util.concurrent.TimeUnit; 

/** 
* Helper class for any drools unit/business tests. 
*/ 
public class DroolsAssert { 

    private class LoggingAgendaEventListener extends DefaultAgendaEventListener { 

     @Override 
     public void beforeMatchFired(BeforeMatchFiredEvent event) { 
      String ruleName = event.getMatch().getRule().getName(); 
      out.println(format("==> '%s' has been activated by the tuple %s", ruleName, event.getMatch().getObjects())); 

      Integer ruleActivations = rulesActivations.get(ruleName); 
      if (ruleActivations == null) { 
       rulesActivations.put(ruleName, 1); 
      } else { 
       rulesActivations.put(ruleName, ruleActivations + 1); 
      } 
     } 
    } 

    private class LoggingWorkingMemoryEventListener extends DefaultRuleRuntimeEventListener { 
     @Override 
     public void objectInserted(ObjectInsertedEvent event) { 
      Object fact = event.getObject(); 
      if (!factsInsertionOrder.containsKey(fact)) { 
       factsInsertionOrder.put(fact, factsInsertionOrder.size()); 
      } 
      out.println(format("--> inserted '%s'", fact)); 
     } 

     @Override 
     public void objectDeleted(ObjectDeletedEvent event) { 
      out.println(format("--> retracted '%s'", event.getOldObject())); 
     } 

     @Override 
     public void objectUpdated(ObjectUpdatedEvent event) { 
      out.println(format("--> updated '%s' \nto %s", event.getOldObject(), event.getObject())); 
     } 
    } 

    private final class FactsInsertionOrderComparator implements Comparator<Object> { 
     @Override 
     public int compare(Object o1, Object o2) { 
      return factsInsertionOrder.get(o1).compareTo(factsInsertionOrder.get(o2)); 
     } 
    } 

    public static final StatefulKnowledgeSession newStatefulKnowladgeSession(Class<?> clazz, String drl, Map<String, String> properties) { 
     KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); 
     kbuilder.add(ResourceFactory.newClassPathResource(drl, clazz), ResourceType.DRL); 

     if (kbuilder.hasErrors()) { 
      throw new Error(kbuilder.getErrors().toString()); 
     } 

     KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); 
     kbase.addKnowledgePackages(kbuilder.getKnowledgePackages()); 

     KieSessionConfiguration config = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(); 
     for (Map.Entry<String, String> property : properties.entrySet()) { 
      config.setProperty(property.getKey(), property.getValue()); 
     } 

     return kbase.newStatefulKnowledgeSession(config, null); 
    } 

    private StatefulKnowledgeSession session; 
    private DefaultAgenda agenda; 
    private SessionPseudoClock clock; 
    private Map<String, Integer> rulesActivations = new ConcurrentHashMap<>(); 
    private Map<Object, Integer> factsInsertionOrder = new IdentityHashMap<>(); 

    public DroolsAssert(Class<?> clazz, String drl) { 
     this(newStatefulKnowladgeSession(clazz, drl, ImmutableMap.of(
       "drools.eventProcessingMode", "stream", 
       "drools.clockType", "pseudo"))); 
    } 

    public DroolsAssert(StatefulKnowledgeSession session) { 
     this.session = session; 
     agenda = (DefaultAgenda) session.getAgenda(); 
     clock = session.getSessionClock(); 
     session.addEventListener(new LoggingAgendaEventListener()); 
     session.addEventListener(new LoggingWorkingMemoryEventListener()); 
    } 

    public void dispose() { 
     session.dispose(); 
    } 

    public void advanceTime(long amount, TimeUnit unit) { 
     clock.advanceTime(amount, unit); 
    } 

    /** 
    * Asserts the only rules listed have been activated no more no less. 
    */ 
    public void assertActivations(String... expected) { 
     Map<String, Integer> expectedMap = new HashMap<>(); 
     for (String rule : expected) { 
      expectedMap.put(rule, 1); 
     } 
     assertActivations(expectedMap); 
    } 

    /** 
    * Asserts the only rules listed have been activated no more no less.<br> 
    * Accepts the number of activations to assert. 
    */ 
    public void assertActivations(Map<String, Integer> expectedActivations) { 
     Map<String, Integer> expected = new HashMap<>(expectedActivations); 
     synchronized (session.getSessionClock()) { 
      for (Map.Entry<String, Integer> actual : rulesActivations.entrySet()) { 
       if (!expected.containsKey(actual.getKey())) { 
        fail(format("'%s' should not be activated", actual.getKey())); 
       } else if (!expected.get(actual.getKey()).equals(actual.getValue())) { 
        fail(format("'%s' should be activated %s time(s) but actially it was activated %s time(s)", actual.getKey(), expected.get(actual.getKey()), actual.getValue())); 
       } else { 
        expected.remove(actual.getKey()); 
       } 
      } 

      if (!expected.isEmpty()) { 
       fail(format("These should be activated: %s", expected.keySet())); 
      } 
     } 
    } 

    /** 
    * Asserts the only rules listed will be activated no more no less.<br> 
    * Waits for scheduled rules if any. 
    */ 
    public void awaitForActivations(String... expected) { 
     Map<String, Integer> expectedMap = new HashMap<>(); 
     for (String rule : expected) { 
      expectedMap.put(rule, 1); 
     } 
     awaitForActivations(expectedMap); 
    } 

    /** 
    * Asserts the only rules listed will be activated no more no less.<br> 
    * Waits for scheduled rules if any.<br> 
    * Accepts the number of activations to assert. 
    */ 
    public void awaitForActivations(Map<String, Integer> expected) { 
     // awaitForScheduledActivations(); 
     assertActivations(expected); 
    } 

    /** 
    * Await for all scheduled activations to be activated to {@link #printFacts()} thereafter for example. 
    */ 
    public void awaitForScheduledActivations() { 
     if (agenda.getScheduledActivations().length != 0) { 
      out.println("awaiting for scheduled activations"); 
     } 
     while (agenda.getScheduledActivations().length != 0) { 
      advanceTime(50, MILLISECONDS); 
     } 
    } 

    public void assertNoScheduledActivations() { 
     assertTrue("There few more scheduled activations.", agenda.getScheduledActivations().length == 0); 
    } 

    /** 
    * Asserts object was successfully inserted to knowledge base. 
    */ 
    public void assertExists(Object objectToMatch) { 
     synchronized (session.getSessionClock()) { 
      Collection<? extends Object> sessionObjects = session.getObjects(); 
      Collection<? extends Object> exists = Collections2.filter(sessionObjects, Equivalence.identity().equivalentTo(objectToMatch)); 
      assertFalse("Object was not found in the session " + objectToMatch, exists.isEmpty()); 
     } 
    } 

    /** 
    * Asserts object was successfully retracted from knowledge base. 
    * 
    * @param obj 
    */ 
    public void assertRetracted(Object retracted) { 
     synchronized (session.getSessionClock()) { 
      Collection<? extends Object> sessionObjects = session.getObjects(); 
      Collection<? extends Object> exists = Collections2.filter(sessionObjects, Equivalence.identity().equivalentTo(retracted)); 
      assertTrue("Object was not retracted from the session " + exists, exists.isEmpty()); 
     } 
    } 

    /** 
    * Asserts all objects were successfully retracted from knowledge base. 
    */ 
    public void assertAllRetracted() { 
     synchronized (session.getSessionClock()) { 
      List<Object> facts = new LinkedList<>(session.getObjects()); 
      assertTrue("Objects were not retracted from the session " + facts, facts.isEmpty()); 
     } 
    } 

    /** 
    * Asserts exact count of facts in knowledge base. 
    * 
    * @param factCount 
    */ 
    public void assertFactCount(long factCount) { 
     synchronized (session.getSessionClock()) { 
      assertEquals(factCount, session.getFactCount()); 
     } 
    } 

    public void setGlobal(String identifier, Object value) { 
     session.setGlobal(identifier, value); 
    } 

    public <T> T execute(Command<T> command) { 
     return session.execute(command); 
    } 

    public List<FactHandle> insert(Object... objects) { 
     List<FactHandle> factHandles = new LinkedList<>(); 
     for (Object object : objects) { 
      out.println("inserting " + object); 
      factHandles.add(session.insert(object)); 
     } 
     return factHandles; 
    } 

    public int fireAllRules() { 
     out.println("fireAllRules"); 
     return session.fireAllRules(); 
    } 

    public List<FactHandle> insertAndFire(Object... objects) { 
     List<FactHandle> result = new LinkedList<>(); 
     for (Object object : objects) { 
      result.addAll(insert(object)); 
      fireAllRules(); 
     } 
     return result; 
    } 

    public void printFacts() { 
     synchronized (session.getSessionClock()) { 
      List<Object> sortedFacts = new LinkedList<>(session.getObjects()); 
      sort(sortedFacts, new FactsInsertionOrderComparator()); 
      out.println(format("Here are %s session facts in insertion order: ", session.getFactCount())); 
      for (Object fact : sortedFacts) { 
       out.println(fact); 
      } 
     } 
    } 
}