2010-10-05 36 views
9

Quelle est la meilleure pratique pour tester les règles bave avec junit?Drools tests avec junit

Jusqu'à présent, nous utilisions junit avec dbunit pour tester les règles. Nous avions des exemples de données qui ont été mis à hsqldb. Nous avions quelques paquets de règles et à la fin du projet, il est très difficile de faire un bon test pour tester certaines règles et ne pas en déclencher d'autres.

Donc la question exacte est que comment puis-je limiter les tests dans junit à une ou plusieurs règle (s) à tester?

Merci pour l'aide,

Hubidubi

Répondre

6

Personnellement, j'utilise des tests unitaires pour tester des règles isolées.Je ne pense pas qu'il y ait quelque chose qui ne va pas, tant que vous ne tombez pas dans un faux sentiment de sécurité que votre base de connaissances fonctionne parce que des règles isolées fonctionnent. Tester toute la base de connaissances est plus important.

Vous pouvez écrire les essais de séparation avec AgendaFilter et 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. 

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

4

Ne tentez pas de limiter la règle d'exécution à une seule règle pour un test. Contrairement aux classes OO, les règles uniques ne sont pas indépendantes des autres règles, il n'est donc pas logique de tester une règle isolément de la même manière que vous testeriez une seule classe à l'aide d'un test unitaire. En d'autres termes, pour tester une seule règle, vérifiez qu'elle a le bon effet en combinaison avec les autres règles. Au lieu de cela, exécutez des tests avec une petite quantité de données sur toutes vos règles, c'est-à-dire avec un nombre minimal de faits dans la session de règles, et testez les résultats et peut-être qu'une règle particulière a été déclenchée. Le résultat n'est pas vraiment différent de ce que vous avez en tête, car un ensemble minimal de données de test peut seulement activer une ou deux règles. En ce qui concerne les données d'échantillon, je préfère utiliser des données statiques et définir des données de test minimales pour chaque test. Il existe plusieurs façons de le faire, mais la création par programmation d'objets de faits dans Java peut être suffisante.

+0

oui je sais comment l'exécution de la règle fonctionne. C'est comme ça que nous le faisons maintenant. Mon problème est avec cette approche qu'il est très difficile de faire suffisamment de données de test appropriées. Comme nous ne limitons pas les règles exécutables, toutes les autres règles peuvent s'exécuter et modifier le résultat final. Il est donc difficile de prédire le résultat final pour les affirmations. C'est la raison pour laquelle j'ai pensé qu'il vaudrait mieux tester les règles izolées. – Hubidubi

+0

Je suppose que j'essayais de dire que le fait que «toutes les autres règles puissent s'exécuter et changer le résultat final» est exactement pourquoi le test d'une règle isolée est moins significatif. –

4

Un test unitaire avec DBUnit ne fonctionne pas vraiment. Un test d'intégration avec DBUnit. Voici pourquoi: - Un test unitaire devrait être rapide. - Une restauration de base de données DBUnit est lente. Prend 30 secondes facilement. - Une application du monde réel a plusieurs colonnes non nulles. Ainsi, les données, isolées pour une seule fonction, utilisent encore facilement la moitié des tables de la base de données. - Un test unitaire devrait être isolé. - La restauration de la base de données dbunit pour chaque test pour les isoler présente des inconvénients: --- L'exécution de tous les tests prend des heures (en particulier lorsque l'application se développe), donc personne ne les exécute, donc ils se cassent constamment, donc ils sont désactivés, donc il n'y a pas de test, donc l'application est pleine de bugs. --- Création d'une demi-base de données pour chaque test un travail de création beaucoup, beaucoup de travail de maintenance, peut facilement devenir invalide (en ce qui concerne la validation que les schémas de base de données ne supportent pas, voir Hibernate Validator) mauvais travail de représentation de la réalité. A la place, écrivez des tests d'intégration avec DBunit: - Un DBunit, identique pour tous les tests. Chargez-le une seule fois (même si vous exécutez 500 tests). - Enveloppez chaque test dans une transaction et annulez la base de données après chaque test. La plupart des méthodes utilisent la propagation requise de toute façon. Définissez la valeur de test sale uniquement (pour la réinitialiser dans le test suivant s'il y a un test suivant) uniquement lorsque la propagation est requires_new. - Remplissez cette base de données avec des cas d'angle. N'ajoutez pas plus de cas courants que ce qui est strictement nécessaire pour tester vos règles métier, donc habituellement seulement 2 cas courants (pour pouvoir tester "un à plusieurs"). - Rédiger des tests à l'épreuve du futur: - Ne pas tester le nombre de règles activées ou le nombre de faits insérés. - Testez plutôt si un certain fait inséré est présent dans le résultat. Filtrez le résultat sur une propriété définie sur X (différente de la valeur commune de cette propriété) et testez le nombre de faits insérés avec cette propriété définie sur X.

4

J'ai créé la bibliothèque simple qui aide à écrire des tests unitaires pour Drools. L'une des caractéristiques est exactement ce dont vous avez besoin: déclarer les fichiers DRL particuliers que vous souhaitez utiliser pour votre test unitaire:

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

Pour plus de détails consulter le blog: http://maciejwalkowiak.pl/blog/2013/11/24/jboss-drools-unit-testing-with-junit-drools/

+0

Le lien n'est pas valide: "Serveur introuvable" – snorbi

0

Parfois, vous pouvez pas vérifier changé état de vos faits, en raison de la nature des règles bave, par exemple, ils peuvent appeler des routes de chameaux. Avec l'approbation suivante, vous pouvez vérifier que la règle a été déclenchée, combien de fois et quand. Vous pouvez affirmer toutes les règles déclenchées après l'insertion de certaines données et affirmer qu'aucune règle indésirable n'a été déclenchée. L'approche est basée sur l'implémentation de 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); 
      } 
     } 
    } 
}