QLExpress 规则单元测试框架:上线前自动跑 1000 条用例,拦截逻辑错误!
运营在后台配了一条规则:orderAmount > 10000 && userLevel == 'VIP' 标记为大额订单。配完觉得没问题,直接上线。半小时后客服被用户打爆——所有 VIP 用户的订单都被拦截了,不管金额大小。排查下来发现是规则里多打了个 >,本来应该是 <。就一个字符,损失了几十万。
规则引擎的优点是灵活——运营可以随时改规则,不用开发介入。但灵活的另一面是危险:没有编译器帮你检查,一个手误就上线了。
今天聊聊怎么给规则加上自动化测试——让 1000 条用例在规则上线前跑一遍,逻辑错误当场拦截。
规则引擎的测试为什么难做
传统代码的测试很成熟:JUnit、Mockito、覆盖率工具,流水线上跑一下,绿了就上线。但规则引擎面前,这套流程用不上。
规则不是 Java 代码。它是运营在后台配的一段表达式字符串,比如:
productPrice * 0.8 > competitorPrice && stockQty < 100
这段东西没有编译期检查。你没法给它写 JUnit,因为在 Java 眼里它只是一个字符串。只有等它被 QLExpress 解析执行的时候,逻辑错误才会暴露——而那时候已经在生产环境了。
所以规则测试的核心挑战就一个:在规则生效之前,用一批预定义的输入和预期输出,验证规则的行为是否符合预期。
思路:把规则测试做成数据驱动
既然规则是表达式字符串,那就用"输入 → 期望输出"的表格来驱动测试:
规则: productPrice * discount > 100 ? "HIGH" : "LOW"
| 用例 | productPrice | discount | 期望结果 |
|------|-------------|----------|----------|
| TC01 | 500 | 0.9 | HIGH |
| TC02 | 100 | 0.9 | LOW |
| TC03 | 200 | 0.5 | LOW |
| TC04 | 1000 | 0.8 | HIGH |
每条用例就是一行数据:给规则需要的变量赋好值,执行规则,拿结果跟期望值比对。一致就通过,不一致就拦截。
这套思路把测试从"写代码"变成了"填表格"。运营和 QA 可以直接在 Excel 或者后台页面里维护测试用例,不用懂 Java。
框架设计:三个角色搞定一切
整体设计上分三个角色:
用例定义器
测试用例的来源。可以是一个 JSON 文件、Excel 表格、或者数据库表。
{
"ruleId": "risk_score_v2",
"rule": "orderAmount * riskFactor > threshold ? 'REJECT' : 'PASS'",
"cases": [
{
"name": "高危大额订单",
"params": {"orderAmount": 50000, "riskFactor": 0.8, "threshold": 30000},
"expected": "REJECT"
},
{
"name": "低危小额订单",
"params": {"orderAmount": 1000, "riskFactor": 0.2, "threshold": 30000},
"expected": "PASS"
},
{
"name": "边界值测试",
"params": {"orderAmount": 37500, "riskFactor": 0.8, "threshold": 30000},
"expected": "PASS"
}
]
}
每一条用例包含三个核心字段:输入参数、期望结果,以及一个可读的用例名称方便排查。
规则执行器
封装 QLExpress 的执行逻辑。接收规则表达式和参数,返回执行结果。
执行流程:
1. 创建 ExpressRunner 实例
2. 把 params 里的变量逐个 put 到 context 里
3. runner.execute(rule, context, ...)
4. 捕获异常 → 记录失败原因
5. 返回结果
用例运行器
遍历所有用例,逐条执行,收集结果。
用例运行器:
加载规则和用例列表 → 逐条执行
│
├─ 执行成功 + 结果匹配 → 通过
├─ 执行成功 + 结果不匹配 → 失败(记录期望 vs 实际)
└─ 执行异常 → 失败(记录异常信息)
最后输出报告:
总计: 1000
通过: 987
失败: 13
通过率: 98.7%
用例怎么造:别只测正常流程
用例的质量决定了这套框架的威力。如果用例全是正常输入,那跟没测区别不大。
必须要有的几类用例
边界值:变量的最小值、最大值、临界点。比如 amount > 10000,一定要测 9999.99、10000、10000.01。
空值和 null:规则里引用的变量可能会缺失。如果运营配了规则但某个字段没传,QLExpress 会抛异常还是静默返回 null?这个行为必须测试覆盖。
类型边界:QLExpress 对数值类型比较敏感。amount > 10000 这个规则,如果传进来的是字符串 "10000" 而不是数字,行为是否符合预期?每种类型至少测一条。
组合爆炸:多个条件组合时,用正交法覆盖。比如规则有三个独立条件,每个条件两种取值,不需要测 2³ = 8 条,用正交表 4 条就能覆盖主要组合。
运营改规则时自动生成回归用例
如果运营在后台改了一条规则,可以自动对比新旧规则在同一个用例集上的执行结果。结果不一致的用例,自动标出来提醒运营确认。这样改规则的时候就能看到"改了这行会影响哪些场景"。
CI 集成:上线前的最后一道防线
用例和数据都准备好了,最后一步是把它嵌入发布流程:
规则变更流程:
运营在后台修改规则 → 点击"提交"
│
├─ 自动触发用例测试
│ ├─ 全部通过 → 规则保存,进入审核
│ └─ 有失败 → 拒绝提交,展示失败详情
│
├─ 审核人审批
│ └─ 通过 → 规则发布到生产
│
└─ 发布后监控
└─ 线上实际命中率 vs 用例预期命中率对比
关键点在第二步——自动化测试是提交的前置条件。用例没全绿,规则就进不了审核流程。这一步在代码层面很简单:提供一个 HTTP 接口或者一个命令行工具,管理后台调用它,返回通过/失败。
实战演示:一个完整的测试框架骨架
核心就三个类,几十行代码:
// 用例定义
class TestCase:
name: string
params: Map
expected: any
// 规则执行器
class RuleExecutor:
execute(rule, params):
context = new DefaultContext()
for each (key, value) in params:
context.put(key, value)
return runner.execute(rule, context, null, true, false)
// 用例运行器
class RuleTestRunner:
run(rule, cases):
report = new TestReport()
for each case in cases:
try:
actual = executor.execute(rule, case.params)
if actual.equals(case.expected):
report.pass(case)
else:
report.fail(case, case.expected, actual)
catch Exception e:
report.error(case, e.message)
return report
TestReport 最终输出一个结构化的结果,包含总数、通过数、失败数,以及每条失败用例的详细信息。前端可以直接渲染成表格,运营一眼就能看到哪些用例没过、实际结果是什么。
总结
规则引擎的测试,说到底是把"规则"这个运行时才确定逻辑的字符串,重新放回编译期的质量保障体系里。
三个角色——用例定义器管数据、规则执行器管引擎、用例运行器管流程——各司其职,拼起来就是一个完整的规则测试框架。
关键点就一个:用例是规则的"类型检查器"。 编译器给你的代码做的检查,用过用例来给规则做。数据维度覆盖够了——边界值、空值、类型、组合——规则的逻辑错误在上线前就能被拦截。
这套框架落地的成本很低,QLExpress 本身就是轻量级的,整个测试框架几百行代码就能搞定。但收益很大——你省的是一次规则事故的赔偿金。
有用的话转给还在让运营裸配规则直接上线的同事。
标题:QLExpress 规则单元测试框架:上线前自动跑 1000 条用例,拦截逻辑错误!
作者:jiangyi
地址:http://jiangyi.space/articles/2026/06/08/1780754420143.html
公众号:服务端技术精选
评论