2010-10-05 17 views
9

Thực hành tốt nhất để kiểm tra quy tắc drools với junit là gì?Thực hiện phép thử với junit

Cho đến bây giờ, chúng tôi đã sử dụng junit với dbunit để kiểm tra các quy tắc. Chúng tôi đã có dữ liệu mẫu được đặt vào hsqldb. Chúng tôi đã có vài gói quy tắc và vào cuối của dự án rất khó để làm cho một đầu vào thử nghiệm tốt để kiểm tra quy tắc nhất định và không bắn những người khác.

Vì vậy, câu hỏi chính xác là làm thế nào tôi có thể giới hạn các bài kiểm tra trong junit cho một hoặc nhiều quy tắc nhất định để kiểm tra?

Nhờ sự giúp đỡ,

Hubidubi

Trả lời

6

Cá nhân tôi sử dụng kiểm tra đơn vị để kiểm tra các quy tắc riêng biệt.Tôi không nghĩ rằng có bất cứ điều gì quá sai với nó, miễn là bạn không rơi vào một cảm giác sai lầm về an ninh mà cơ sở tri thức của bạn đang làm việc bởi vì các quy tắc riêng biệt đang hoạt động. Kiểm tra toàn bộ cơ sở tri thức là quan trọng hơn.

Bạn có thể viết các bài kiểm tra cách ly với AgendaFilter và 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. 

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

4

Đừng cố gắng để hạn chế thực thi quy tắc để một quy tắc duy nhất cho một thử nghiệm. Không giống như các lớp OO, các quy tắc đơn không độc lập với các quy tắc khác, do đó không có nghĩa là kiểm tra quy tắc một cách giống như cách bạn sẽ kiểm tra một lớp đơn bằng cách sử dụng kiểm tra đơn vị. Nói cách khác, để kiểm tra một quy tắc duy nhất, hãy kiểm tra xem nó có tác dụng đúng hay không kết hợp với các quy tắc khác. Thay vào đó, hãy chạy thử nghiệm với một lượng nhỏ dữ liệu trên tất cả các quy tắc của bạn, tức là với số lượng sự kiện tối thiểu trong phiên quy tắc và kiểm tra kết quả và có thể quy tắc cụ thể đã được kích hoạt. Kết quả không thực sự khác nhiều so với những gì bạn nghĩ, vì một tập dữ liệu thử nghiệm tối thiểu chỉ có thể kích hoạt một hoặc hai quy tắc.

Đối với dữ liệu mẫu, tôi thích sử dụng dữ liệu tĩnh hơn và xác định dữ liệu thử nghiệm tối thiểu cho mỗi thử nghiệm. Có nhiều cách khác nhau để làm điều này, nhưng lập trình tạo các đối tượng thực tế trong Java có thể đủ tốt.

+0

vâng tôi biết làm thế nào thực thi quy tắc hoạt động. Đây là cách chúng ta làm điều đó ngay bây giờ. Vấn đề của tôi là với cách tiếp cận này là rất khó để tạo đủ dữ liệu thử nghiệm thích hợp. Bởi vì chúng tôi không giới hạn các quy tắc runnable, bất kỳ quy tắc khác có thể chạy và thay đổi kết quả cuối cùng. Vì vậy, thật khó để dự đoán kết quả cuối cùng cho các khẳng định. Đó là lý do tại sao tôi nghĩ rằng nó sẽ là tốt hơn để kiểm tra các quy tắc izolated. – Hubidubi

+0

Tôi đoán tôi đã cố gắng nói rằng thực tế là 'bất kỳ quy tắc nào khác có thể chạy và thay đổi kết quả cuối cùng' là chính xác lý do tại sao thử nghiệm quy tắc trong sự cô lập ít có ý nghĩa hơn. –

4

Kiểm tra đơn vị với DBUnit không thực sự hiệu quả. Một thử nghiệm tích hợp với DBUnit. Đây là lý do tại sao: - Một bài kiểm tra đơn vị phải nhanh chóng. - Khôi phục cơ sở dữ liệu DBUnit chậm. Mất 30 giây một cách dễ dàng. - Ứng dụng trong thế giới thực có nhiều cột không phải là null. Vì vậy, dữ liệu, bị cô lập cho một tính năng duy nhất, vẫn dễ dàng sử dụng một nửa các bảng của cơ sở dữ liệu. - Một bài kiểm tra đơn vị nên được phân lập. - Khôi phục cơ sở dữ liệu dbunit cho mọi thử nghiệm để giữ cho chúng bị cô lập có nhược điểm: --- Chạy tất cả các kiểm tra mất nhiều giờ (đặc biệt là khi ứng dụng phát triển), vì vậy không ai chạy chúng, vì vậy chúng liên tục bị hỏng, vì vậy chúng bị vô hiệu hóa, vì vậy không có thử nghiệm, vì vậy bạn ứng dụng có đầy đủ các lỗi. --- Tạo một nửa cơ sở dữ liệu cho mỗi bài kiểm tra đơn vị là rất nhiều công việc sáng tạo, rất nhiều công việc bảo trì, có thể dễ dàng trở thành không hợp lệ (liên quan đến việc xác nhận lược đồ cơ sở dữ liệu nào không hỗ trợ, xem Hibernate Validator) và thường thực hiện công việc xấu của đại diện cho thực tế.

Thay vào đó, hãy viết bài kiểm tra tích hợp với DBunit: - Một DBunit, giống nhau cho tất cả các bài kiểm tra. Tải nó chỉ một lần (ngay cả khi bạn chạy 500 bài kiểm tra). - Gói mỗi bài kiểm tra trong một giao dịch và khôi phục cơ sở dữ liệu sau mỗi lần kiểm tra. Tuy nhiên, hầu hết các phương thức đều sử dụng tuyên truyền. Chỉ đặt giá trị của dữ liệu thử nghiệm (để đặt lại trong thử nghiệm tiếp theo nếu có thử nghiệm tiếp theo) chỉ khi yêu cầu truyền là không cần thiết. - Điền vào cơ sở dữ liệu đó với các trường hợp góc. Không thêm các trường hợp phổ biến hơn là cần thiết để kiểm tra các quy tắc kinh doanh của bạn, vì vậy thường chỉ có 2 trường hợp phổ biến (để có thể kiểm tra "một đến nhiều"). - Viết các bài kiểm tra tương lai: - Không kiểm tra số lượng các quy tắc được kích hoạt hoặc số lượng các sự kiện được chèn vào. - Thay vào đó, hãy kiểm tra xem có sự kiện được chèn vào nào đó trong kết quả không. Lọc kết quả trên một thuộc tính nhất định được đặt thành X (khác với giá trị chung của thuộc tính đó) và kiểm tra số lượng sự kiện được chèn với thuộc tính đó được đặt thành X.

4

Tôi tạo ra thư viện đơn giản giúp để viết các unit test cho Drools. Một trong những tính năng là chính xác những gì bạn cần: khai báo file DRL đặc biệt mà bạn muốn sử dụng cho kiểm tra đơn vị của bạn:

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

Để biết thêm chi tiết có một cái nhìn vào bài viết trên blog: http://maciejwalkowiak.pl/blog/2013/11/24/jboss-drools-unit-testing-with-junit-drools/

+0

Liên kết không hợp lệ: "Không tìm thấy máy chủ" – snorbi

0

Đôi khi bạn không thể kiểm tra thay đổi trạng thái của sự thật yor, vì bản chất của quy tắc drools, ví dụ họ có thể gọi các tuyến lạc đà. Với apprach sau đây bạn có thể kiểm tra wethere quy tắc đã được bắn, bao nhiêu lần, và khi nào. Bạn có thể khẳng định tất cả các quy tắc được kích hoạt sau khi chèn một số sự kiện và khẳng định rằng không có quy tắc không mong muốn nào được kích hoạt. Phương pháp tiếp cận dựa trên việc triển khai 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); 
      } 
     } 
    } 
}