桂云网络:桂花流程引擎(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 - 测试数据库类型:h2
21:46:18.297 [main] INFO com.osgit.bpmn.core.MyTest - 测试日志记录等级:full
21: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
更多推荐

所有评论(0)