桂云网络:桂花流程引擎(Osmanthus)开发实现单元测试原理及使用示例_桂花流程引擎怎么样
篇章引言
桂花流程引擎(Osmanthus),是基于Camunda 7.20+扩展,旨在满足国内精细化的审批需求的国产流程引擎。桂花流程引擎为桂云网络公司旗下的标准化流程产品,之所以取名为“桂花”,其一是因为桂云网络公司主商标“桂云”的实物化产品品牌延伸,其二是因为桂花是中国传统文化名花,其淡雅的清香给人一种古典而唯美的体验感官。桂花流程引擎中“桂花”为桂云网络公司指定在商标尼斯分类第0901组软件类产品的商标,本文由桂云网络OSG独家贡献。
关键字: 桂云网络, 桂云, OSG, 桂花流程引擎, 桂花流程, 桂花, bpmn
在桂云网络公司开发桂花流程引擎的过程中,单元测试是非常重要的一环,因为流程引擎测试必须要走完完整的流程引擎初始化、流程图部署、流程流转到指定状态、流程清除等过程,如果不使用单元测试,那么在开发过程中将会非常耗时。本篇文章主要说明单元测试的工作原理和使用示例。
Camunda单元测试类的继承关系
单元测试,一般使用扩展自PluggableProcessEngineTestCase类,其继承路径为以下关系,直至继承junit的TestCase类
TestCase <- PvmTestCase <- AbstractProcessEngineTestCase <- PluggableProcessEngineTestCase
其中PvmTestCase并没有定义很多接口,只定义了两个断言函数和一个默认手动激活规则。
public class PvmTestCase extends TestCase { // 断言是否包含文字 public void assertTextPresent(String expected, String actual) { if ( (actual==null) || (actual.indexOf(expected)==-1) ) { throw new AssertionFailedError(\"expected presence of [\"+expected+\"], but was [\"+actual+\"]\"); } } // 断言是否包含指定文字,忽略大小写 public void assertTextPresentIgnoreCase(String expected, String actual) { assertTextPresent(expected.toLowerCase(), actual.toLowerCase()); } public Object defaultManualActivation() { Expression expression = new FixedValue(true); CaseControlRuleImpl caseControlRule = new CaseControlRuleImpl(expression); return caseControlRule; }}
再来看AbstractProcessEngineTestCase,忽略非关键函数
public abstract class AbstractProcessEngineTestCase extends PvmTestCase { // 流程引擎实例 protected ProcessEngine processEngine; // 流程部署 protected String deploymentId; protected Set<String> deploymentIds = new HashSet<>(); protected Throwable exception; protected ProcessEngineConfigurationImpl processEngineConfiguration; // 各类的流程引擎service protected RepositoryService repositoryService; protected RuntimeService runtimeService; protected TaskService taskService; protected FormService formService; protected HistoryService historyService; protected IdentityService identityService; protected ManagementService managementService; protected AuthorizationService authorizationService; protected CaseService caseService; protected FilterService filterService; protected ExternalTaskService externalTaskService; protected DecisionService decisionService; // 初始化流程引擎 protected abstract void initializeProcessEngine(); // 关闭流程引擎 protected void closeDownProcessEngine() { } // 单元测试用例的逻辑 @Override public void runBare() throws Throwable { // 调用初始化流程引擎 initializeProcessEngine(); if (repositoryService==null) { // 初始化流程引擎的各种service initializeServices(); } try { // 通过注解来获取历史记录等级 boolean hasRequiredHistoryLevel = TestHelper.annotationRequiredHistoryLevelCheck(processEngine, getClass(), getName()); // 通过注解获取数据库配置 boolean runsWithRequiredDatabase = TestHelper.annotationRequiredDatabaseCheck(processEngine, getClass(), getName()); // ignore test case when current history level is too low or database doesn\'t match if (hasRequiredHistoryLevel && runsWithRequiredDatabase) { // 单元测试之前通过@Deployment注解自动部署 deploymentId = TestHelper.annotationDeploymentSetUp(processEngine, getClass(), getName()); // 调用单元测试用例的逻辑 super.runBare(); } } catch (AssertionFailedError e) { LOG.error(\"ASSERTION FAILED: \" + e, e); exception = e; throw e; } catch (Throwable e) { LOG.error(\"EXCEPTION: \" + e, e); exception = e; throw e; } finally { // 测试完毕 // 清除认证 identityService.clearAuthentication(); processEngineConfiguration.setTenantCheckEnabled(true); // 删除部署 deleteDeployments(); // 删除Job deleteHistoryCleanupJobs(); // 断言数据库是否已经清空 TestHelper.assertAndEnsureCleanDbAndCache(processEngine, exception == null); // 重置ID生成器 TestHelper.resetIdGenerator(processEngineConfiguration); // 重置时间 ClockUtil.reset(); // 关闭流程引擎 closeDownProcessEngine(); // 清除流程引擎的各种service clearServiceReferences(); } } protected void deleteHistoryCleanupJobs() { final List<Job> jobs = historyService.findHistoryCleanupJobs(); for (final Job job: jobs) { processEngineConfiguration.getCommandExecutorTxRequired().execute(new Command<Void>() { public Void execute(CommandContext commandContext) { commandContext.getJobManager().deleteJob((JobEntity) job); return null; } }); } } protected void deleteDeployments() { if(deploymentId != null) { deploymentIds.add(deploymentId); } for(String deploymentId : deploymentIds) { TestHelper.annotationDeploymentTearDown(processEngine, deploymentId, getClass(), getName()); } deploymentId = null; deploymentIds.clear(); } // 初始化流程引擎的各种service protected void initializeServices() { processEngineConfiguration = ((ProcessEngineImpl) processEngine).getProcessEngineConfiguration(); repositoryService = processEngine.getRepositoryService(); runtimeService = processEngine.getRuntimeService(); taskService = processEngine.getTaskService(); formService = processEngine.getFormService(); historyService = processEngine.getHistoryService(); identityService = processEngine.getIdentityService(); managementService = processEngine.getManagementService(); authorizationService = processEngine.getAuthorizationService(); caseService = processEngine.getCaseService(); filterService = processEngine.getFilterService(); externalTaskService = processEngine.getExternalTaskService(); decisionService = processEngine.getDecisionService(); } // 清除流程引擎的各种service protected void clearServiceReferences() { processEngineConfiguration = null; repositoryService = null; runtimeService = null; taskService = null; formService = null; historyService = null; identityService = null; managementService = null; authorizationService = null; caseService = null; filterService = null; externalTaskService = null; decisionService = null; } // 其余}
由以上可以看出,AbstractProcessEngineTestCase只是一个抽象类,不能直接实例化,因此再看PluggableProcessEngineTestCase的源码
public class PluggableProcessEngineTestCase extends AbstractProcessEngineTestCase { // 缓存的流程引擎实例 protected static ProcessEngine cachedProcessEngine; // 流程引擎初始化 protected void initializeProcessEngine() { processEngine = getOrInitializeCachedProcessEngine(); } // 流程引擎初始化 private static ProcessEngine getOrInitializeCachedProcessEngine() { if (cachedProcessEngine == null) { try { cachedProcessEngine = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource(\"camunda.cfg.xml\") .buildProcessEngine(); } catch (RuntimeException ex) { if (ex.getCause() != null && ex.getCause() instanceof FileNotFoundException) { cachedProcessEngine = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource(\"activiti.cfg.xml\") .buildProcessEngine(); } else { throw ex; } } } return cachedProcessEngine; } // 获取流程引擎实例 public static ProcessEngine getProcessEngine() { return getOrInitializeCachedProcessEngine(); }}
该类的只有流程引擎的配置与初始化,配置读取camunda.cfg.xml配置文件,或者如果以上文件不存在,则读取activiti.cfg.xml配置文件,并最终完成初始化获得processEngine实例。
如何使用
先看一个读取流程引擎配置信息的例子,这个测试案例获取流程引擎的数据库类型和日志记录等级。
package com.osgit.bpmn.core;import org.camunda.bpm.engine.test.ProcessEngineTestCase;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class MyTest extends ProcessEngineTestCase { private final static Logger LOGGER = LoggerFactory.getLogger(MyTest.class); public void testDatabaseAndHistoryLevel() { LOGGER.info(\"测试数据库类型:{}\", this.processEngine.getProcessEngineConfiguration().getDatabaseType()); LOGGER.info(\"测试日志记录等级:{}\", this.processEngine.getProcessEngineConfiguration().getHistory()); }}
直接运行该testDatabaseAndHistoryLevel函数,将得到以下结果,结果是运营正常且正确的
=========================================================================21:46:18.297 [main] INFO com.osgit.bpmn.core.MyTest - 测试数据库类型:h221:46:18.297 [main] INFO com.osgit.bpmn.core.MyTest - 测试日志记录等级:full21:46:18.297 [main] DEBUG org.camunda.bpm.engine.test - annotation @Deployment deletes deployment for MyTest.testDatabaseAndHistoryLevel
再来看一个带测试部署的案例,我们bpmn文件案例存放在java的resources目录内,那么将单元测试的@Deployment注解到单元测试函数上,运行单元测试前,就会自动在本单元测试响应目录寻找MyTest.testDeploymentAndGetTaskName.bpmn20.xml文件,部署后,在单元测试中,使用runtimeService.startProcessInstanceByKey启动流程即可,这样就实现了桂花流程引擎的单元测试
package com.osgit.bpmn.core;import org.camunda.bpm.engine.runtime.ProcessInstance;import org.camunda.bpm.engine.task.Task;import org.camunda.bpm.engine.test.Deployment;import org.camunda.bpm.engine.test.ProcessEngineTestCase;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class MyTest extends ProcessEngineTestCase { private final static Logger LOGGER = LoggerFactory.getLogger(MyTest.class); @Deployment public void testDeploymentAndGetTaskName() { ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(\"Process_036276n\"); Task task = taskService.createTaskQuery().singleResult(); LOGGER.info(\"测试任务节点: {}\", task.getName()); }}
运行结果如下
22:21:30.022 [main] INFO com.osgit.bpmn.core.MyTest - 测试任务节点: 任务一22:21:30.023 [main] DEBUG org.camunda.bpm.engine.test - annotation @Deployment deletes deployment for MyTest.testDeploymentAndGetTaskName
作者: 桂云网络, 桂云, OSG