使用 Camunda DMN JUnit 5 扩展在测试中注入 DMN 引擎:依赖配置、注入方式与自定义引擎配置实战
【免费下载链接】camunda-bpm-platformCamunda 7 CE is End of Life (EoL). Please check out Camunda 8 instead (https://github.com/camunda/camunda) or read about Camunda 7 Enterprise End of Life (https://camunda.com/blog/2025/02/camunda-7-enterprise-end-of-life-extension/) – Camunda 7 CE was a flexible framework for workflow and decision automation using BPMN and DMN.项目地址: https://gitcode.com/GitHub_Trending/ca/camunda-bpm-platform
<输出文章>
Camunda Platform DMN JUnit 5 扩展实战:在测试中注入 DMN 引擎的完整指南
本指南围绕 test-utils/junit5-extension-dmn/README.md 展开,系统讲解如何通过camunda-dmn-junit5这个 JUnit 5 扩展,在测试类中快速获得一个可用的DmnEngine实例。文章覆盖 Maven 依赖引入、@ExtendWith字段注入与方法参数注入两种使用姿势,并结合仓库源码剖析扩展的实现原理,最后介绍通过@RegisterExtension+forConfiguration使用自定义DmnEngineConfiguration的高级用法。读完本文,你可以在不依赖流程引擎、不编写样板代码的情况下,直接对 DMN 决策模型编写单元测试。
1. 扩展是什么:一句话定位
camunda-dmn-junit5是 Camunda Platform 提供的一个轻量级 JUnit 5 扩展(Extension),核心能力只有一件事:在 JUnit 5 测试中创建一个DmnEngine并注入到测试类字段或测试方法参数中。它与面向流程引擎的 camunda-bpm-junit5 定位互补——前者注入ProcessEngine,后者专门服务于DmnEngine,两者都可与camunda-engine-dmn(engine-dmn/engine)配合使用。
该扩展的实现只有一个类 DmnEngineExtension.java,同时实现了 JUnit 5 的TestInstancePostProcessor、BeforeTestExecutionCallback与ParameterResolver三个扩展接口,从而同时支持字段注入和参数注入两条路径。
2. 快速上手
2.1 添加 Maven 依赖
在pom.xml中加入如下依赖:
<dependency> <groupId>org.camunda.bpm.dmn</groupId> <artifactId>camunda-dmn-junit5</artifactId> <version>7.20.0</version> <scope>test</scope> </dependency>要点说明:
<scope>test</scope>是必须的:该扩展只在测试编译与运行期使用,不应被打进生产制品。- 版本号请按你使用的 Camunda 发行版对齐。当前仓库对应版本为 7.24.0-SNAPSHOT(见 pom.xml 中的 parent 版本),README 示例中的
7.20.0是可用的社区版坐标之一。 - 扩展对
camunda-engine-dmn与 JUnit Jupiter 均以provided作用域声明(见 pom.xml),这意味着你的测试工程需要自行引入camunda-engine-dmn(获得DmnEngine类型)和 JUnit 5(获得@ExtendWith等注解);工程自身则使用junit-bom统一管理 JUnit 5 版本(5.9.3,见 pom.xml)。 - 如果仅想测试 DMN 引擎本身,可以直接引入 engine-dmn/engine 模块(
org.camunda.bpm.dmn:camunda-engine-dmn),它自带DmnEngine、DmnEngineConfiguration与测试用 DmnEngineRule。
2.2 编写第一个测试:@ExtendWith+ 字段注入
import org.camunda.bpm.dmn.engine.DmnEngine; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(DmnEngineExtension.class) class MyDmnTest { // 声明一个字段,扩展会自动注入 DmnEngine 实例 public DmnEngine dmnEngine; @Test void shouldEvaluateDecision() { // 直接使用注入的 dmnEngine 解析并求值决策 } }2.3 方法参数注入:按需获取
如果不想声明字段,也可以把它作为测试方法参数,扩展会按类型匹配自动注入:
@Test void testDecision(DmnEngine dmnEngine) { // 使用 dmnEngine }字段注入与参数注入可以混用,选择哪种取决于代码风格;仓库自带的测试 DmnEngineExtensionTest.java 就同时演示了两种方式(shouldInjectClassField用字段、shouldInjectMethodParameter用参数),并且两个测试共用同一个注入实例完成相同的决策求值断言。
3. 一个可以跑通的完整示例
参考 DmnEngineExtensionTest.java 与仓库内的 DMN 样例文件 DecisionWithLiteralExpression.dmn,一个完整可运行的测试如下:
package org.camunda.bpm.dmn.engine.test.junit5; import static org.assertj.core.api.Assertions.assertThat; import static org.camunda.bpm.engine.variable.Variables.createVariables; import org.camunda.bpm.dmn.engine.DmnDecisionResult; import org.camunda.bpm.dmn.engine.DmnEngine; import org.camunda.commons.utils.IoUtil; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(DmnEngineExtension.class) class DmnEngineExtensionTest { public static final String DMN_MINIMAL = "org/camunda/bpm/dmn/engine/test/junit5/DecisionWithLiteralExpression.dmn"; // 字段注入 DmnEngine dmnEngineField; @Test void shouldInjectClassField() { shouldEvaluateDecisionWithLiteralExpression(dmnEngineField); } // 方法参数注入 @Test void shouldInjectMethodParameter(DmnEngine dmnEngineParam) { shouldEvaluateDecisionWithLiteralExpression(dmnEngineParam); } protected void shouldEvaluateDecisionWithLiteralExpression(DmnEngine dmnEngine) { DmnDecisionResult result = dmnEngine.evaluateDecision( dmnEngine.parseDecision("decision", IoUtil.fileAsStream(DMN_MINIMAL)), createVariables() .putValue("a", 2) .putValue("b", 3)); assertThat(result.getSingleResult()).containsOnlyKeys("c"); assertThat((int) result.getSingleEntry()).isEqualTo(5); } }对应 DMN 决策模型(位于 engine-dmn/engine/src/test/resources/org/camunda/bpm/dmn/engine/api/DecisionWithLiteralExpression.dmn):
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" id="definitions" name="Definitions" namespace="http://camunda.org/schema/1.0/dmn"> <decision id="decision" name="Decision with Literal Expression"> <variable name="result" typeRef="string" /> <literalExpression> <text>input == "ok" ? "ok" : "notok"</text> </literalExpression> </decision> </definitions>这个示例展示了使用注入的DmnEngine的典型三步流程:
parseDecision("decision", stream):按决策id解析 DMN 模型,得到DmnDecision(DmnEngine接口定义见 DmnEngine.java);createVariables().putValue(...):通过 typed-values 模块的Variables工厂构建求值变量;evaluateDecision(...):执行求值并返回DmnDecisionResult,随后用 AssertJ 对结果做断言。
4. 注入机制原理:源码级解读
DmnEngineExtension的类声明如下(DmnEngineExtension.java):
public class DmnEngineExtension implements TestInstancePostProcessor, BeforeTestExecutionCallback, ParameterResolver {三条注入路径分别对应三个接口回调:
- 字段注入(实例字段):
postProcessTestInstance在测试实例创建后被调用,通过反射扫描测试类中类型为DmnEngine的字段并赋值(injectIntoTestInstance使用getDeclaredFields()遍历,即使字段是private也能注入,因为injectDmnEngine会调用field.setAccessible(true))。 - 字段注入(static 字段):
beforeTestExecution在每个测试方法执行前再次执行同样的字段注入逻辑,因此即使DmnEngine字段是static的,也能在测试执行前被正确赋值——这由ExtensionContext.getTestInstance()拿到实例后注入实现。 - 参数注入:
supportsParameter仅当参数类型严格等于DmnEngine.class时返回true;resolveParameter在类型匹配时初始化引擎并返回该实例。注意它是精确类型匹配,子类或接口类型不会被注入。
引擎创建采取惰性初始化:initializeDmnEngine()只有在dmnEngine == null时才调用dmnEngineConfiguration.buildEngine()构建,因此同一个扩展实例在同一测试生命周期内复用一个引擎实例,不会为每个测试方法重复创建。
注入的类型判断可见 DmnEngineExtension.java(参数解析)与 DmnEngineExtension.java(字段注入)。
5. 高级用法:使用自定义 DMN 引擎配置
默认情况下,扩展使用DmnEngineConfiguration.createDefaultDmnEngineConfiguration()创建默认配置(DmnEngineConfiguration.java)。当测试需要自定义配置(例如注册自定义表达式语言、自定义DmnDecisionEvaluationListener、设置引擎指标收集器等)时,可以使用静态工厂方法forConfiguration配合@RegisterExtension:
import org.camunda.bpm.dmn.engine.DmnEngineConfiguration; import org.camunda.bpm.dmn.engine.impl.DefaultDmnEngineConfiguration; import org.junit.jupiter.api.extension.RegisterExtension; class RegisterDmnEngineExtensionTest { private DmnEngineConfiguration customConfiguration = new DefaultDmnEngineConfiguration(); @RegisterExtension private DmnEngineExtension dmnEngineExtension = DmnEngineExtension.forConfiguration(customConfiguration); // 之后被注入的 DmnEngine 将基于 customConfiguration 构建 }forConfiguration的实现(DmnEngineExtension.java)对空配置做了防御:
public static DmnEngineExtension forConfiguration(DmnEngineConfiguration configuration) { return new DmnEngineExtension( Objects.requireNonNull(configuration, "configuration must not be null")); }对应测试 RegisterDmnEngineExtensionTest.java 验证了三点行为:
- 传入
null配置会抛出带信息configuration must not be null的NullPointerException; - 注入到
static字段、实例字段以及方法参数中的引擎,其getConfiguration()都与传入的自定义配置对象相同(assertThat(...getConfiguration()).isEqualTo(customConfiguration)); - 三种注入方式(static 字段、实例字段、方法参数)行为一致。
需要注意:DmnEngineConfiguration的 Javadoc 明确指出,对配置实例的修改也会影响已由该配置构建的引擎(见 DmnEngineConfiguration.java),因此在测试中复用配置对象时要注意状态共享。
此外,DmnEngineConfiguration还提供了丰富的可配置项,例如引擎指标收集器setEngineMetricCollector、决策表求值监听器customPreDecisionTableEvaluationListeners等(见 DmnEngineConfiguration.java),这些都可以在测试中按需装配。
6. 与 DmnEngineRule(JUnit 4)的对比
如果你的项目仍在使用 JUnit 4,仓库同样提供了基于 Rule 的等效方案 DmnEngineRule.java。它与DmnEngineExtension的对应关系非常直接:DmnEngineRule也支持默认配置(构造时调用createDefaultDmnEngineConfiguration())与自定义配置两种构造方式,并在内部通过dmnEngineConfiguration.buildEngine()构建引擎(见 DmnEngineRule.java)。
从源码结构看,两个类共享同一套引擎构建与配置注入思路,只是接入点从 JUnit 4 的TestRule换成了 JUnit 5 的ExtensionAPI。对于新项目,推荐直接使用camunda-dmn-junit5。
7. 版本与维护说明
依据 pom.xml 中模块描述:7.24.0 是发布到 Maven Central 的最后一个社区版,该库后续不再发布新版本。Camunda 7 社区版已进入 End of Life(EoL)阶段,项目 README(README.md)也明确提示用户转向 Camunda 8。因此本文介绍的能力基于当前仓库源码(7.24.0-SNAPSHOT 时代)验证,在迁移到 Camunda 8 时请以新平台的测试工具为准。
8. 小结
| 使用方式 | 关键代码 | 适用场景 |
|---|---|---|
| 默认引擎 + 字段注入 | @ExtendWith(DmnEngineExtension.class)+public DmnEngine dmnEngine; | 大多数 DMN 决策单测 |
| 默认引擎 + 参数注入 | @Test void test(DmnEngine dmnEngine) {...} | 只需在个别测试中使用引擎 |
| 自定义配置 | @RegisterExtension+DmnEngineExtension.forConfiguration(cfg) | 需要监听器、自定义 EL、指标收集器等 |
| JUnit 4 | DmnEngineRule | 存量 JUnit 4 项目 |
camunda-dmn-junit5将「创建引擎 + 注入实例」的样板代码压缩为零:依赖坐标一行、类注解一行、字段或参数一行。配合仓库内的 DmnEngineExtensionTest.java 与 RegisterDmnEngineExtensionTest.java 两个测试用例,你可以直接作为模板开启 DMN 决策的单元测试之旅。
【免费下载链接】camunda-bpm-platformCamunda 7 CE is End of Life (EoL). Please check out Camunda 8 instead (https://github.com/camunda/camunda) or read about Camunda 7 Enterprise End of Life (https://camunda.com/blog/2025/02/camunda-7-enterprise-end-of-life-extension/) – Camunda 7 CE was a flexible framework for workflow and decision automation using BPMN and DMN.项目地址: https://gitcode.com/GitHub_Trending/ca/camunda-bpm-platform
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考