jBPM5:BPMN2 Processの単体テスト

渡邊です。こんにちは。

jBPM5の研究をしています。
備忘録を兼ねて、BPMN2 Processの単体テストについてまとめます。

Guvnor、TaskService、DBなどを使わずに、オフラインでBPMN2 Processの分岐網羅テストをしました。また、異なる経路を辿る複数のプロセスを並列に走らせ、整合性をテストしました(おまけ程度)。

まずは、次のBPMN2 Processをご覧ください。

お小遣い申請フローです。Guvnorを使わずに、JBoss Developer Studioで作成しました。図からは読み取れませんが、各User Taskの主なプロパティは次の通りです。
注意点は、タスク「No」は、Script Taskであることです(残念でしたという旨のメールが自動で送信される想定です)。

NameTaskNameActorIdParameter MappingResult Mapping
RequestRequestTaskhusbandrequestId=requestId
ResponseResponseTaskwiferequestId=requestIdoutcome=outcome
ReceiveReceiveTaskhusbandrequestId=requestId

作成したJUnitのテストコードはこちらです。
 

必要なJAR

テストコードの作成、およびテストの実行も、JBoss Developer Studioを使いました。
droolsjbpm-integration-distribution-xxxあたりに同梱されたJARをやみくもにビルドパスに追加すると、JARが多すぎる為か、JUnitテストを実行できません。
必要なJARは次の通りです(もっと減らせるかもしれませんが、それは目的ではないので、この辺で)。
バージョンはよしなに読み替えてください。

  • antlr-3.3.jar
  • antlr-runtime-3.3.jar
  • commons-beanutils-1.7.0.jar
  • commons-collections-3.2.1.jar
  • commons-compress-1.0.jar
  • commons-digester-1.8.jar
  • commons-exec-1.0.1.jar
  • commons-io-1.4.jar
  • commons-jexl-1.1.jar
  • commons-lang-2.4.jar
  • commons-logging-1.1.1.jar
  • commons-logging-api-1.1.jar
  • commons-net-2.0.jar
  • drools-compiler-5.5.0.Final.jar
  • drools-core-5.5.0.Final.jar
  • ecj-3.5.1.jar
  • jbpm-bpmn2-5.4.0.Final.jar
  • jbpm-flow-5.4.0.Final.jar
  • jbpm-flow-builder-5.4.0.Final.jar
  • jbpm-human-task-core-5.4.0.Final.jar
  • jbpm-human-task-hornetq-5.4.0.Final.jar
  • jbpm-workitems-5.4.0.Final.jar
  • knowledge-api-5.5.0.Final.jar
  • knowledge-internal-api-5.5.0.Final.jar
  • mvel2-2.1.3.Final.jar
  • slf4j-api-1.6.4.jar
  • slf4j-simple-1.6.1.jar

ちょっと脱線するので省きますが、ecj-3.5.1.jarに関連して、こちらに興味深い記事がありました。
 

テスト用のWorkItemHandler

WorkItemを内部のListに溜め込むだけのWorkItemHandlerです。このWorkItemHandlerを使い、プロセス内に存在するHuman Taskの数、あるHuman Task実行後における次のHuman Taskの状態を検証するテストコードを記述します。

    public class MockHumanTaskWorkItemHandler implements WorkItemHandler {
        /**
         * executeWorkItemメソッドで受けた全てのWorkItemを保持するList
         */
        private List workItems;
        public MockHumanTaskWorkItemHandler() {
            workItems = Collections.synchronizedList(new ArrayList());
        }
        @Override
        public void executeWorkItem(WorkItem workItem, WorkItemManager manager) {
            workItems.add(workItem);
        }
        @Override
        public void abortWorkItem(WorkItem workItem, WorkItemManager manager) {
            manager.abortWorkItem(workItem.getId());
        }
        /**
         * @return 最後に追加されたWorkItem
         */
        public WorkItem lastWorkItem() {
            return workItems.get(workItems.size() - 1);
        }
        public int getWorkItemSize() {
            return workItems.size();
        }
    }

 

セッション取得

テスト用のWorkItemHandlerをWorkItemManagerに登録します。この時に指定する文字列は、”Human Task”でなければなりません。

    @BeforeClass
    public static void beforeClass() {
        KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        builder.add(ResourceFactory.newClassPathResource("giveMeMoneyProcess.bpmn"), ResourceType.BPMN2);
        knowledgeBase = builder.newKnowledgeBase();
    }

    @Before
    public void before() {
        workItemHandler = new MockHumanTaskWorkItemHandler();
        ksession = knowledgeBase.newStatefulKnowledgeSession();
        ksession.getWorkItemManager().registerWorkItemHandler("Human Task", workItemHandler);
    }

 

プロセス開始とHuman Task完了

プロセス開始メソッド(startProcess)は最初のHuman Taskを、Human Task完了メソッド(completeWorkItem)は、次のHuman Task(終了ノードに達した場合はnull)を返すようにしています。
 

1経路あたりのテスト

分岐網羅を100%にする為に必要なプロセス数は、2ですが、せっかくなので3プロセスに分けてテストしました。
次のテストコードは、 お小遣いの取得に一発で成功する経路のテストです。「Receive」完了後は、Uset Taskが増えていないことを確認しています。

    /**
     * お小遣い取得成功テスト
     */
    @Test
    public void testSuccessProcess() {
        doTestSuccessProcess(true);
    }
        /**
     * お小遣い取得成功テストの本体
     */
    private void doTestSuccessProcess(boolean checkWorkItemNum) {
        final String requestId = "1";

        WorkItem request = startProcess(requestId);
        assertWorkItem(request, checkWorkItemNum, 1, "RequestTask", "husband", requestId);

        WorkItem response = completeWorkItem(request, null);
        assertWorkItem(response, checkWorkItemNum, 2, "ResponseTask", "wife", requestId);

        WorkItem receive = completeWorkItem(response, "yes");
        assertWorkItem(receive, checkWorkItemNum, 3, "ReceiveTask", "husband", requestId);

        WorkItem end = completeWorkItem(receive, null);
        Assert.assertNull(end);
        assertWorkItemNum(checkWorkItemNum, 3);
    }

 

並列処理

全経路テストが済んだあとで、3つのプロセスを並列で走らせ、全プロセス終了後の整合性をテストしました。といっても、この例では最終的なWorkItemの数を検証しただけです。
ものによっては、もっと面白いテストを作成できると思います。

この程度のテストで確認できることは、BRMS5の開発者が検証しているはずですが、自分自身で試すことにより、BRMS5の素晴らしさを改めて体感しました。

    @Test
    public void testCouponProcessMultiThread() {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        pool.execute(new Runnable() {
            @Override
            public void run() {
                doTestRetryProcess(false);
            }
        });
        pool.execute(new Runnable() {
            @Override
            public void run() {
                doTestFailureProcess(false);
            }
        });
        pool.execute(new Runnable() {
            @Override
            public void run() {
                doTestSuccessProcess(false);
            }
        });

        boolean isSuccessful = false;
        try {
            pool.shutdown();
            isSuccessful = pool.awaitTermination(1, TimeUnit.MINUTES);
            if (!isSuccessful) pool.shutdownNow();
        } catch (InterruptedException e) {
            pool.shutdownNow();
        }
        // タイムアウトしないで終了していること
        Assert.assertEquals(true, isSuccessful);
        // 全スレッドが正しく終了していれば、タスクの数は次の期待値であること
        assertWorkItemNum(true, 10);
    }

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です