///|
test {
let manifest = @moon_mutest.manifest("a + b == c && false", file="demo.mbt")
inspect(manifest.summary.candidate_count, content="4")
inspect(manifest.candidates[0].rule.label, content="add-to-sub")
let mutants = @moon_mutest.generate_mutants("a + b == c", file="demo.mbt")
inspect(mutants[0].source, content="a - b == c")
}
生成 JSON manifest:
///|
test {
let text = @moon_mutest.format_manifest_json("a == b", file="demo.mbt")
assert_true(text.contains("\"candidate_count\": 1"))
assert_true(text.contains("\"eq-to-ne\""))
}
多文件项目规划:
///|
test {
let plan = @moon_mutest.plan_project([
@moon_mutest.source_file("src/a.mbt", "a == b"),
@moon_mutest.source_file("src/b.mbt", "x + y && false"),
])
inspect(plan.file_count, content="2")
inspect(plan.mutation_count, content="4")
inspect(plan.mutations[0].global_id, content="0")
}
生成 patch preview:
///|
test {
let source = "let ok = a == b"
let candidate = @moon_mutest.discover(source, file="a.mbt")[0]
let preview = @moon_mutest.preview_candidate_patch(source, candidate)
assert_true(preview is Some(_))
}
///|
test {
let config = @moon_mutest.MutestConfig::default()
let plan = @moon_mutest.build_execution_plan(
[
@moon_mutest.source_file("src/a.mbt", "a == b && false"),
@moon_mutest.source_file("src/b.mbt", "x + y"),
],
config,
)
let selected = @moon_mutest.select_executions(
plan,
Shard(shard_index=1, shard_count=2),
)
inspect(selected.length(), content="2")
let batches = @moon_mutest.build_selected_batch_plan(
plan,
FirstMutants(2),
OneMutantPerBatch,
)
inspect(batches.batch_count, content="2")
let script = @moon_mutest.generate_selected_runner_script(
plan,
FirstMutants(1),
BashDialect,
)
assert_true(script.content.contains("moon-mutest baseline"))
}
这部分能力可以用于:
把大型项目的 mutation run 拆成多个 CI 分片。
只跑本次变更附近的 mutant。
为 nightly job 生成批处理计划。
给质量门禁提供稳定输入。
Workspace 文件选择
默认只选择生产源码,跳过测试文件和生成文件。需要时可以显式打开这些选项。
///|
test {
let files = [
@moon_mutest.source_file("src/lib.mbt", "a == b"),
@moon_mutest.source_file("src/lib_test.mbt", "test {}"),
@moon_mutest.source_file("_build/gen/out.mbt", "let x = 1"),
]
let selected = @moon_mutest.select_workspace_files(
files,
@moon_mutest.WorkspaceFileSpec::moonbit_default(),
)
inspect(selected.length(), content="1")
inspect(selected[0].path, content="src/lib.mbt")
}
质量门禁
项目级报告可以接入质量门禁,用于 CI 中判断是否允许合并。
///|
test {
let plan = @moon_mutest.plan_project([
@moon_mutest.source_file("src/a.mbt", "a == b"),
])
let report = @moon_mutest.summarize_project_run(plan, [
@moon_mutest.project_result(plan.mutations[0], Killed),
])
let gate = @moon_mutest.evaluate_quality_gate(
report,
@moon_mutest.QualityGate::default(),
)
inspect(@moon_mutest.quality_gate_status_label(gate.status), content="passed")
}
moon_mutest
moon_mutest是一个面向 MoonBit 项目的变异测试工具包。它会扫描 MoonBit 源码,生成保守的变异点,把每个变异点应用到真实项目的临时副本中, 再执行moon check/moon test,最后汇总每个 mutant 是被测试杀死、 逃逸、编译失败、超时还是被跳过。这个项目的目标不是替代
moon test,而是回答一个更尖锐的问题:项目同时提供两层能力:
仓库地址:
项目背景
普通单元测试通常只告诉我们“当前测试是否通过”。覆盖率工具可以告诉我们 “哪些行被执行过”。但它们都不一定能证明测试具有足够的断言能力。
变异测试会主动制造小型缺陷,例如:
==改成!=&&改成||true改成false<改成<=+改成-如果测试失败,说明这个变异被“杀死”,测试对这类缺陷有感知能力。如果测试仍然 通过,说明该 mutant “逃逸”,对应位置可能缺少断言、边界用例或业务约束测试。
MoonBit 生态正在快速发展,项目会越来越依赖自动化测试和 CI。
moon_mutest尝试补上 MoonBit 质量基础设施中“测试强度评估”这一块。当前能力
当前版本已经实现了从源码扫描到真实 workspace 自动运行的闭环:
moon check/moon test才继续跑 mutants。moon check/moon test或自定义命令。快速开始
先确认已经安装 MoonBit 工具链,并且命令行可以使用
moon。克隆仓库后,在项目根目录运行:
扫描一段源码:
示例输出:
对真实 MoonBit 项目运行变异测试:
如果你想先保守地试跑,可以限制 mutant 数量:
CLI 用法
scan
scan用来扫描一段源码字符串,不会访问真实项目文件。它适合快速查看某个表达式 会生成哪些 mutation。使用边界规则集:
输出 JSON,方便 CI 或其他工具消费:
run
run会读取真实 MoonBit workspace,复制一份临时目录,然后在临时目录中执行 变异测试。默认命令是:常用参数:
--profile basic|boundary|experimental:选择规则集。--format text|markdown|json:选择报告格式。--max-mutants N:规划阶段最多保留 N 个 mutants。--first N:只执行前 N 个 mutants。--id-start A --id-end B:执行[A, B)范围内的 mutant id。--include-tests:把*_test.mbt/*_wbtest.mbt也作为变异目标。--include-generated:包含生成文件。--keep-temp:保留临时 workspace,方便调试。--no-fail-fast:对同一个 mutant 执行所有配置命令。--temp-dir PATH:指定临时 workspace 路径。--check-command "...":自定义 check 阶段命令。--test-command "...":自定义 test 阶段命令。示例:
输出 JSON:
保留临时目录:
结果含义
每个 mutant 会被归类为以下状态之一:
Killed:测试阶段失败,说明测试发现了这个变异。Survived:所有命令通过,说明这个变异逃逸,测试可能不够强。CompileError:check/build 阶段失败,变异导致代码无法编译。Timeout:命令超过时间预算。Skipped:runner 没有得到可用执行结果。通常最需要关注的是
Survived。它们往往意味着:支持的变异规则
默认
basic规则集包含:true <-> false== <-> !=<,<=,>,>=的边界替换+,-,*,/&& <-> ||boundary规则集额外包含数字边界变异:0 -> 11 -> 0-1 -> 0规则设计偏保守,优先保证输出可解释、可复现、便于调试。后续可以继续扩展到模式匹配、 函数调用、集合操作、错误处理分支等更高阶的 MoonBit 语义规则。
库 API 示例
除了 CLI,
moon_mutest也可以作为 MoonBit 库使用。生成 JSON manifest:
多文件项目规划:
生成 patch preview:
真实 workspace runner
真实项目执行是本项目最关键的部分。
runner/子包和cmd/main/CLI 使用 JS target, 原因是它们需要 Node 环境提供文件系统和进程执行能力。执行流程如下:
.mbt文件。_build/、生成文件、测试文件等目标。--keep-temp。这个设计避免直接修改用户项目,也让每次运行都更容易复现。
CI 规划能力
moon_mutest不只关注本地一次性运行,也提供适合 CI 的规划 API。这部分能力可以用于:
Workspace 文件选择
默认只选择生产源码,跳过测试文件和生成文件。需要时可以显式打开这些选项。
质量门禁
项目级报告可以接入质量门禁,用于 CI 中判断是否允许合并。
质量门禁可以围绕这些指标设置:
仓库结构
根包是对外 facade,主要 re-export 各功能子包的公开 API。核心代码按职责拆分:
core/:扫描器、规则、过滤和 mutant 生成。io/:manifest、JSON 和文本输出。plan/:项目规划、workspace 文件选择。run/:patch、执行计划、项目报告、baseline、脚本和质量门禁。runner/:JS/Node workspace 复制、mutation 应用、进程执行和真实结果汇总。cmd/main/:命令行入口。tests/:黑盒测试,覆盖根包公开 API。docs/:项目说明、比赛材料和仓库结构文档。更详细的结构说明见
docs/repository_layout.md。开发与验证
推荐在提交前运行:
根包和大部分规划、报告逻辑保持平台无关,方便测试。真实 workspace runner 依赖 JS target,因为它需要调用 Node 的文件系统和子进程能力。
比赛契合点
本项目面向 MoonBit 国产开源生态建设,重点补充测试质量基础设施。它和现有
moon test、moon coverage的关系是互补的:moon test检查测试是否通过。moon_mutest检查测试是否能发现被注入的语义缺陷。对于 CCF 开源创新大赛 / MoonBit 生态方向,这个项目的价值在于:
当前边界
当前版本仍然保持 MVP 取向,规则以源码级保守替换为主。它不会尝试理解所有 MoonBit 语义,也不会保证每个 mutant 都一定有业务意义。
已知边界包括:
这些边界不会影响当前工具用于发现测试薄弱点,但后续仍有很明确的演进空间。
License
Apache-2.0.