Слюни тестирование с джунитом

Какова наилучшая практика для проверки правил слюни с помощью 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);
            }
        }
    }
}
Другие вопросы по тегам