Слюни тестирование с джунитом
Какова наилучшая практика для проверки правил слюни с помощью junit?
До сих пор мы использовали junit с dbunit для проверки правил. У нас были образцы данных, которые были помещены в hsqldb. У нас было несколько пакетов правил, и к концу проекта очень трудно сделать хороший тестовый ввод, чтобы протестировать определенные правила и не запускать другие.
Таким образом, точный вопрос заключается в том, как я могу ограничить тесты в junit одним или несколькими определенными правилами для тестирования?
Спасибо за помощь,
Hubidubi
5 ответов
Лично я использую юнит-тесты для проверки отдельных правил. Я не думаю, что с этим что-то не так, если вы не впадаете в ложное чувство безопасности, что ваша база знаний работает, потому что работают изолированные правила. Тестирование всей базы знаний важнее.
Вы можете написать изолирующие тесты с 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
Не пытайтесь ограничить выполнение правила одним правилом для теста. В отличие от ОО-классов, отдельные правила не зависят от других правил, поэтому нет смысла тестировать правило изолированно так же, как если бы вы тестировали один класс с помощью модульного теста. Другими словами, чтобы проверить одно правило, проверьте, что оно имеет правильный эффект в сочетании с другими правилами.
Вместо этого запускайте тесты с небольшим количеством данных по всем вашим правилам, т. Е. С минимальным количеством фактов в сеансе правил, и проверяйте результаты и, возможно, то, что определенное правило было выполнено. Результат на самом деле не сильно отличается от того, что вы имеете в виду, потому что минимальный набор тестовых данных может активировать только одно или два правила.
Что касается выборочных данных, я предпочитаю использовать статические данные и определять минимальные тестовые данные для каждого теста. Есть разные способы сделать это, но программное создание объектов фактов в Java может быть достаточно хорошим.
Я создал простую библиотеку, которая помогает писать модульные тесты для 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/
Модульный тест с DBUnit на самом деле не работает. Интеграционный тест с DBUnit делает. И вот почему: - модульный тест должен быть быстрым. - Восстановление базы данных DBUnit происходит медленно. Легко занимает 30 секунд. - Реальное приложение имеет много не нулевых столбцов. Таким образом, данные, выделенные для одной функции, все еще легко используют половину таблиц базы данных. - Юнит тест должен быть изолирован. - Восстановление базы данных dbunit для каждого теста, чтобы держать их изолированными, имеет недостатки: --- Выполнение всех тестов занимает часы (особенно по мере роста приложения), поэтому никто не запускает их, поэтому они постоянно ломаются, поэтому они отключены, поэтому нет тестирования, поэтому ваше приложение полно ошибок. --- Создание половины базы данных для каждого модульного теста - это большая работа по созданию, большая работа по обслуживанию, которая может легко стать недействительной (что касается проверки, которую не поддерживает схема базы данных, см. Hibernate Validator), и обычно это плохо работа по представлению реальности.
Вместо этого напишите интеграционные тесты с DBunit: - Один DBunit, одинаковый для всех тестов. Загрузите его только один раз (даже если вы запустите 500 тестов). - Завершение каждого теста в транзакции и откат базы данных после каждого теста. Большинство методов используют распространение, необходимое в любом случае. Устанавливать только тестовые данные грязными (чтобы сбросить их в следующем тесте, если есть следующий тест) только тогда, когда для распространения требуется require_new. - Заполните эту базу данных угловыми делами. Не добавляйте больше общих случаев, чем строго необходимо для проверки ваших бизнес-правил, поэтому обычно только 2 общих случая (чтобы иметь возможность проверить "один ко многим"). - Написать тесты на будущее: - Не проверяйте количество активированных правил или количество вставленных фактов. - Вместо этого проверьте, присутствует ли в результате определенный факт. Отфильтруйте результат по определенному свойству, установленному в X (отличается от общего значения этого свойства), и проверьте количество вставленных фактов с этим свойством, установленным в X.
Иногда вы не можете проверить измененное состояние ваших фактов из-за природы правил слюней, например, они могут вызывать верблюжьи маршруты. С помощью следующей инструкции вы можете проверить, было ли применено правило, сколько раз и когда. Вы можете утверждать, что все правила срабатывают после вставки некоторых фактов, и утверждать, что никаких нежелательных правил не сработало. Подход основан на реализации 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);
}
}
}
}