用组合建模给AI探索测试划定空间
Combinatorics-Framework-JVM 将配置、操作顺序和可选事件建模为测试用例,并保留从设计到执行证据的可检查链路。文章强调先定义变化维度与不变量,再让 AI 在明确的测试空间内探索。
AI 深度解读
AI 能写功能、补单测,却未必能说清:这些测试覆盖了哪些交互,漏掉了哪些操作顺序。Combinatorics-Framework-JVM 试图解决的就是这个问题:先把会变化的条件、操作历史和可选事件建成模型,再生成可检查的用例,让 AI 根据失败证据修复代码。
它的价值不在于替你猜未知 bug,而在于把测试范围和结果留下来。不过,原文展示的缺陷都是预先植入的,距离证明真实项目中的找错效果,还有一段距离。
参数组合覆盖了,不代表操作历史覆盖了
作者建议先回答三个问题:什么会变,什么必须始终成立,以及愿意运行多少用例。
以结账为例,账户类型、支付方式、功能开关属于静态条件;提交、重试的先后顺序属于操作历史;超时、重复请求、价格变化则属于可能发生、也可能不发生的事件。
这些东西不能全塞进一张参数表。两次请求使用相同参数,第一次直接失败,与支付方已受理但响应超时,是两段不同的历史。后一种情况下重试,可能产生重复扣款。
因此,框架分别建模静态上下文、操作序列和可选事件,再把序列与事件同选中的上下文交叉组合。静态部分可以按声明的交互强度缩减,时间线则需要单独表达。
参数相同,不代表系统经历相同。
约束也需要人判断。逻辑上不可能出现的组合可以排除;应用应该拒绝的有效输入,例如通过 API 请求货到付款,应当保留为负向测试。把后者删掉,会连同验证拒绝行为的机会一起删掉。
原文提到一个结账例子有 24 种静态组合,但没有列出完整因素,无法据此复原模型。下面这个小例子更清楚。
先读懂生成的用例,再让它执行
框架提供桌面应用 guif、命令行和嵌入 API。下面的 first-scenario.json 表达两种模式,以及从三个动作中选两个不同动作并排列:
{
"spec_version": "1",
"title": "Two modes and ordered pairs of actions",
"slots": [
{
"sheet": "MODE",
"alias": "choose_one",
"raw": true,
"values": ["safe", "fast"]
},
{
"sheet": "OPS",
"alias": "permute(2)",
"flags": ["FW_Concatenator="],
"raw": true,
"values": ["A", "B", "C"]
}
]
}
关键是 permute(2):AB、BA、AC、CA、BC、CB 都是独立顺序,与两种模式组合后得到 2 × 6 = 12 个候选。
配套的 first-scenario.properties 设置为 cf.coverage.strength=0、cf.coverage.budget=0 和 cf.plan.autoPromoteSingleVerb=true。将文件与 fw-app.jar 放在工作目录,使用 JDK 25 执行:
java -Djava.awt.headless=true -jar fw-app.jar validate --scenario first-scenario.json --config first-scenario.properties
java -Djava.awt.headless=true -jar fw-app.jar plan --scenario first-scenario.json --config first-scenario.properties
java -Djava.awt.headless=true -jar fw-app.jar candidates --scenario first-scenario.json --config first-scenario.properties --limit 12
注意最后的 candidates 命令:这里输出的是帮助理解模型的纯文本候选,还没有执行被测程序。作者强调先验证、查看计划、检查候选,再运行。偏好表格的团队也可以使用数据工作表和 FW_Seq 控制表表达模型。
真正落地时,还得补上两样东西。适配器负责连接系统、复位状态、控制依赖;测试判定器(oracle)负责检查不变量,也就是每段历史结束后必须成立的业务规则。
例如,同一个请求键最多产生一笔扣款和一个订单,应该检查持久化业务数据及其关联,只看最终 HTTP 状态不够。框架能够生成事件顺序,却不能直接控制线程调度或远程支付方。暂停回调、释放回调、模拟超时,都需要适配器实现。
找到预埋缺陷,证明了什么
作者给出的自测生成 72 个用例,经一条约束排除 24 个,剩余 48 个可执行。结果为 44 个 PASS、4 个 DOMAIN_FAIL,没有测试执行设施故障。验证记录使用 JDK 25 和 0.1.0-SNAPSHOT。
更大的演示包括 4,128 个 Cucumber 场景,运行于 JVM 内的结账引擎;另有 714 个 Selenium 浏览器场景,使用 headless Firefox 测试模拟商店。两套设计分别重跑一次,每个用例的判定都与首次一致。
这些结果说明执行链路能捕获预埋问题,并在演示环境里复现结果。它们没有证明框架能高效发现真实项目中的未知缺陷。
结果读取方式也影响接入:发现产品缺陷时,进程仍可能以退出码 0 结束。因此,CI 不能只检查命令成功与否,还要读取 DOMAIN_FAIL,并与 BROKEN、TIMEOUT、INFRA_FAIL 等执行结果分开处理。
框架会保留候选、判定、测量值及复现上下文,通过 checkpoint 和导出保存证据,并把失败归入问题组。所谓「minimal witness」,只是组内源内容最少的失败用例,并非经过证明的数学最小反例。与较高失败率相关的取值,也只是调查线索。
同样,全部通过只能表述为:在当前模型和交互强度下,没有观察到违规。模型没有表达的变化,仍然没有被验证。
这套方法还能用于 AI 实验。三种检索策略、两套 prompt、两个阈值组成 12 种配置;每种配置测 50 道题、重复两次,就是 1,200 次评估。Pareto front 可以展示成本与质量的取舍,但评价方法仍需自己校准,远程模型的版本、设置和速率限制也要记录。
我的判断
我认可它把操作历史与参数组合分开处理。对重试、回调、重复提交这类问题,单测数量很难说明覆盖程度,一份能预览的时间线模型更方便讨论遗漏。
但它没有省掉测试中最难的工作:确定不变量、控制事件、复位状态。没有可靠断言,生成再多用例也只是运行了一遍。原文没有提供与手写测试、属性测试或 fuzzing 的对照数据,因此效率收益目前属于作者经验与演示,不能直接推广到一般团队。
Pairwise 也有边界:它覆盖模型中的两两取值,不能保证覆盖三因素缺陷,更不自动覆盖顺序和时机。其选择采用贪心策略,不承诺最小用例集;少于三个变化因素时,两两覆盖已经是完整空间,无法缩减。
工程接入还需接受几个具体限制。每个用例只有一个判定,首个失败检查会遮住后续失败,作者建议把每项检查同时记录为 metric。Java 用例在框架自身 JVM 内、以当前权限运行,没有 OS sandbox;Python 用例则独立启动进程,可配合 bwrap 或 Docker 隔离。
项目要求带编译器的完整 JDK 25,因为生成用例会在内存中编译;核心流程不需要 Python、关系数据库或容器,Python 3.11+ 与 Python add-on 是可选项。按原文描述,它在 2026 年 10 月首次公开发布,由一名维护者维护,并以 AS IS、不提供担保为前提,适合先做小范围验证。
AI 委托也不是贴上路径就自动开始。原文中的 AI_DELEGATION.md 要求明确同意,并在定稿设计、执行用例、构建安装及访问网络等动作前取得相应授权,先做 smoke run。作者称许可也把监督要求列为条件,但未提供完整条款,不能据此作进一步法律判断。
能拿来做什么
- 从一个已经修复的重复提交或回调缺陷入手,列三到四个因素、一段麻烦的操作顺序和一个不变量,先确认每个候选都读得懂。
- 在本地支付或订单替身中植入已知缺陷,验证断言确实能抓住它,再修复重跑;前端提示成功之外,还要核对后端订单和扣款状态。
- 向 AI 提交失败候选、请求键、两笔扣款 ID 和不变量,要求它提出符合证据的机制解释,并指出哪些相邻历史能区分不同解释,修复后重跑这些用例。
- 做 prompt 或检索策略比较时,先算完整请求量并校准评价器;如果只有一两个参数、状态无法复位,或普通单测已经能固定缺陷,就继续使用更直接的测试方式。