2010-10-05 3 views
9

Какова наилучшая практика тестирования правил слюни с помощью junit?Испытание дросселей с помощью junit

До сих пор мы использовали junit с dbunit для проверки правил. У нас были образцы данных, которые были помещены в hsqldb. У нас было несколько пакетов правил, и к концу проекта очень сложно сделать хороший тестовый ввод для проверки определенных правил и не запускать других.

Итак, точно вопрос, как я могу ограничить тесты в junit одним или несколькими определенными правилами для тестирования?

Спасибо за помощь,

Hubidubi

ответ

6

Лично я использую модульные тесты для проверки изолированных правил.Я не думаю, что в этом есть что-то слишком плохое, если вы не попадете в ложное чувство безопасности, что ваша база знаний работает, потому что работают изолированные правила. Тестирование всей базы знаний более важно.

Вы можете написать разделительные тесты с AgendaFilter и 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. 

источника Код: http://blog.athico.com/2007/07/my-rules-dont-work-as-expected-what-can.html

4

Не пытайтесь ограничить выполнение правил для одного правила для теста. В отличие от классов OO, одиночные правила не зависят от других правил, поэтому нет смысла тестировать правило изолированно так же, как вы тестируете один класс с помощью модульного теста. Другими словами, чтобы проверить одно правило, проверьте, что он имеет правильный эффект в сочетании с другими правилами.

Вместо этого запустите тесты с небольшим количеством данных по всем вашим правилам, т. Е. С минимальным количеством фактов в сеансе правил, и проверьте результаты и, возможно, что определенное правило было уволено. Результат не настолько сильно отличается от того, что вы имеете в виду, потому что минимальный набор тестовых данных может активировать только одно или два правила.

Что касается данных образца, я предпочитаю использовать статические данные и определять минимальные тестовые данные для каждого теста. Существуют различные способы сделать это, но программное создание объектов фактов в Java может быть достаточно хорошим.

+0

да, я знаю, как работает выполнение правила. Так мы это делаем сейчас. Моя проблема заключается в таком подходе, что очень сложно сделать достаточные и соответствующие тестовые данные. Поскольку мы не ограничиваем выполняемые правила, любые другие правила могут запускаться и изменять окончательный результат. Поэтому трудно предсказать конечный результат для утверждений. Именно по этой причине я подумал, что было бы лучше проверить правила, которые были изложены. – Hubidubi

+0

Я предполагаю, что я пытался сказать, что тот факт, что «любые другие правила могут запускать и изменять окончательный результат», является именно тем, почему тестирование правила в изоляции менее значимо. –

4

Единичный тест с DBUnit на самом деле не работает. Тест интеграции с DBUnit. Вот почему: - Единичный тест должен быть быстрым. - Восстановление базы данных DBUnit происходит медленно. Снимает 30 секунд. - Приложение реального мира имеет много не нулевых столбцов. Таким образом, данные, выделенные для одной функции, все еще легко используют половину таблиц базы данных. - Единичный тест должен быть изолирован. - Восстановление базы данных dbunit для каждого теста, чтобы удержать их в изоляции, имеет недостатки: --- Запуск всех тестов занимает несколько часов (особенно по мере того, как приложение растет), поэтому никто не запускает их, поэтому они постоянно ломаются, поэтому они отключены, поэтому нет тестирования, поэтому приложение заполнено ошибками. --- Создание половины базы данных для каждого модульного теста - это много работы по созданию, большая часть работ по техническому обслуживанию, может легко стать недействительной (в отношении проверки того, какая схема базы данных не поддерживается, см. Hibernate Validator) и обычно выполняет плохая работа по представлению реальности.

Вместо этого введите интеграционные тесты с помощью DBunit: - Один DBunit, тот же для всех тестов. Загрузите его только один раз (даже если вы проведете 500 тестов). - Оберните каждый тест в транзакцию и откатите базу данных после каждого теста. Большинство методов используют распространение в любом случае. Установите только пробные данные (чтобы сбросить его в следующем тесте, если есть следующий тест), только когда требуется распространение. - Заполните эту базу данных угловыми корпусами. Не добавляйте более распространенные случаи, чем это необходимо для проверки ваших бизнес-правил, поэтому обычно используются только 2 распространенных случая (чтобы проверить «один на многие»). - Напишите будущие тесты: - Не проверяйте количество активированных правил или количество вставленных фактов. - Вместо этого проверьте, присутствует ли в результате определенный вставленный факт. Отфильтруйте результат на определенном свойстве, установленном в X (отличном от общего значения этого свойства) и проверьте количество вставленных фактов с этим свойством, установленным на X.

4

Я создал простую библиотеку, которая помогает писать модульные тесты для Drools. Одной из особенностей является то, что вам нужно: объявлять определенные DRL файлы, которые вы хотите использовать для модульного тестирования:

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

Для получения более подробной информации посмотрите на блоге: http://maciejwalkowiak.pl/blog/2013/11/24/jboss-drools-unit-testing-with-junit-drools/

+0

Недействительная ссылка: «Сервер не найден» – snorbi

0

Иногда вы не можете проверить изменен состояние фактов, из-за природы правил слюни, например, они могут звонить по верблюжьим маршрутам. С последующей оценкой вы можете проверить, что правило было уволено, сколько раз и когда. Вы можете утверждать, что все правила уволены после введения некоторых фактов и утверждают, что никаких нежелательных правил не было сделано. Подход основан на реализации 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); 
      } 
     } 
    } 
} 
Смежные вопросы