moon_mutest

    Mutation testing toolkit for MoonBit projects

    mutation-testing
    testing
    quality
    cli
    Download zip
    Author
    Version
    0.1.7
    License
    Apache-2.0
    Last updated
    last month
    Downloads
    35

    Dependencies

    #moon_mutest

    moon_mutest 是 MoonBit 项目的变异测试工具。它把小型代码改动应用到临时 workspace,再运行 moon checkmoon test,用结果判断测试是否真的能发现 代码被改坏。

    它关注的不是“测试有没有运行”,而是“断言是否足够强”。例如把 == 改成 != + 改成 - 后测试仍通过,说明这个 mutant 逃逸,对应代码可能缺少精确断言或 边界用例。

    #安装

    库 API 可以从 mooncakes.io 安装:

    moon add Magic486/moon_mutest@0.1.7

    CLI 目前随源码仓库提供,需要在本仓库根目录执行:

    moon run --target js cmd/main -- scan "a == b && true"

    需要 Node.js 和可用的 moon 命令。CLI 会在临时目录运行,默认不会修改目标项目。

    发布包的下游使用示例位于 examples/consumer_workspace,可在发布后验证:

    moon -C examples/consumer_workspace test

    #最快用法

    先从一个很小的范围开始:

    moon run --target js cmd/main -- run path/to/workspace --max-mutants 10 --first 10

    对全部生产源码运行时,工具默认:

    1. 跳过 *_test.mbt、生成文件和 _build
    2. 复制目标 workspace 到临时目录。
    3. 先执行基线 moon checkmoon test
    4. 逐个应用 mutation 并汇总 killedsurvivedcompile-errortimeoutskippedequivalent

    默认命令可以用 --check-command--test-command 覆盖。

    #增量变异测试

    对日常开发和 PR,推荐只测相对 Git 参考点发生变化的生产源码:

    moon run --target js cmd/main -- run . \ --changed-since origin/master \ --max-mutants 30 \ --first 10

    --changed-since REF 先以 git merge-base REF HEAD 找到共同基线,再使用 git diff --relative --name-only --diff-filter=ACMR BASE -- 获取 新增、复制、修改和重命名后的文件,并包含当前未提交修改。删除文件会被忽略。 报告中的 changed-sincegit-changed-filesincremental-files 会显示实际范围。 使用该参数时,目标 workspace 必须位于可读取参考点的 Git 仓库中。

    本地快速检查上一提交以来的改动:

    moon run --target js cmd/main -- run . --changed-since HEAD~1 --max-mutants 20

    在 CI 中,先确保 Git 历史包含参考分支;GitHub Actions 可以使用 actions/checkoutfetch-depth: 0,然后传入 origin/master。命令会以目标 workspace 作为相对路径根,因此嵌套的 MoonBit workspace 也能正确匹配 Git 文件。

    也可以写入 workspace 根目录的 moon_mutest.json

    { "changed_since": "origin/master", "max_mutants": 30, "first": 10, "fail_under": 80, "max_survived": 0 }

    命令行参数优先于配置文件。未指定 --config 时,会自动读取 workspace/moon_mutest.json

    已人工确认的等价 mutant 可以在配置中注明 id 和原因。它会出现在报告中,但不影响 mutation score:

    { "equivalent": [ { "id": 12, "reason": "该分支在当前域模型中与原逻辑等价" } ] }

    对于整行都不应生成 mutation 的生成代码或兼容代码,可在源码行末写 // mutest:ignore。这是一项显式抑制,不应被用来掩盖 escaped mutant。

    #质量门禁

    将变异测试接入 CI 时,使用质量门禁让不达标的 run 返回非零退出码:

    moon run --target js cmd/main -- run . \ --changed-since origin/master \ --fail-under 80 \ --max-survived 0 \ --max-compile-error 0 \ --max-timeout 0 \ --max-skipped 0

    --strict-gate 是一组保守默认值:score 至少 90,且不允许 survived、 compile-error、timeout 或 skipped。

    仓库内有两个可复现示例:

    # 强断言:预期 killed=1、score=100%、质量门禁通过 moon run --target js cmd/main -- run examples/quality_gate_workspace \ --max-mutants 1 --first 1 --fail-under 100 --max-survived 0 --max-skipped 0 # 弱断言:预期 survived=1、risk=high,并给出补测建议 moon run --target js cmd/main -- run examples/weak_test_workspace \ --max-mutants 1 --first 1

    #报告

    默认文本报告适合终端与 CI 日志。还支持:

    # 供 CI 机器消费 moon run --target js cmd/main -- run . --format json --max-mutants 20 # 供代码评审或归档阅读 moon run --target js cmd/main -- run . --format markdown --max-mutants 20 > mutest-report.md # 可离线打开的总览、文件风险排序与 survived 诊断 moon run --target js cmd/main -- run . --format html --max-mutants 20 > mutest-report.html

    报告会按文件排序风险,并针对 survived mutant 给出建议。例如数值变异逃逸时会提示 补充精确数值断言和边界值测试。

    #常用参数

    参数用途
    --profile basic\|boundary\|experimental选择变异规则集。
    --changed-since REF只测相对 Git 参考点发生变化的生产文件。
    --max-mutants N / --first N限制规划或实际执行数量。
    --id-start A --id-end B执行半开区间 [A, B) 的 mutant id。
    --include-tests也把测试文件作为 mutation 目标。
    --include-generated包含生成的 MoonBit 文件。
    --keep-temp / --temp-dir PATH保留或指定临时 workspace,便于排障。
    --no-fail-fast一个 mutant 执行全部命令,而不是在首次有效信号后停止。
    --format text\|markdown\|json\|html选择报告格式。
    --fail-under--max-*启用质量门禁。

    #作为库使用

    ///|
    test {
    let manifest = @moon_mutest.manifest("a == b && true", file="demo.mbt")
    inspect(manifest.summary.candidate_count, content="3")
    }

    根包还提供扫描、规则过滤、项目计划、批次/分片选择、报告和质量门禁 API;详情可查看 生成的 API 文档或 repository layout

    #开发与 CI

    提交前运行:

    moon fmt moon info moon check --target all moon test --target all git diff --exit-code

    GitHub Actions 位于 .github/workflows/ci.yml,覆盖 moon checkmoon test moon fmtmoon info、CLI 示例、质量门禁和 HTML 报告。Gitlink 代码流水线可使用 仓库根目录的 Jenkinsfile

    当前 MoonBit 工具链若不支持 moon fmt --deny-warnmoon info --deny-warn,CI 会 自动使用对应的最新可用命令,再通过 git diff --exit-code 验证格式与接口文件没有未提交改动。

    #边界与许可证

    • 当前 CLI 使用 JS/Node 后端执行真实 workspace;扫描与规划库支持 MoonBit 的常规后端。
    • 变异测试会增加 CI 时间,建议 PR 使用 --changed-since--first,全量扫描放到 nightly。
    • Apache-2.0,见 LICENSE

    BaselineOutcome

    BaselineReport

    BatchStrategy

    BatchValidation

    CommandPhase

    CommandResult

    ExecutionBatch

    ExecutionPlan

    FileMutationPlan

    FileRunReport

    MutantOutcome

    MutantResult

    MutationCandidate

    MutationExecution

    MutationFilter

    MutationManifest

    MutationRunReport

    MutationSummary

    MutestConfig

    PatchPreview

    ProjectMutantResult

    ProjectMutation

    ProjectMutationPlan

    ProjectRunReport

    QualityGate

    QualityGateReport

    QualityGateStatus

    QualityGateViolation

    ReportFormat

    RuleCatalogEntry

    RuleSetValidation

    RunnerCommand

    RunnerScript

    SelectionMode

    SelectionReport

    SelectionValidation

    ShellDialect

    WorkspaceFileCategory

    WorkspaceFileDecision

    WorkspaceFileSpec

    WorkspaceSelectionReport

    apply_edit

    fn apply_edit(source : String, edit :
    TextEdit
    ) -> String?

    apply_mutation

    fn apply_mutation(source : String, candidate :
    MutationCandidate
    ) -> String

    baseline_can_run

    fn baseline_can_run(report :
    BaselineReport
    ) -> Bool

    baseline_outcome_label

    fn baseline_outcome_label(outcome :
    BaselineOutcome
    ) -> String

    boundary_literal_rules

    candidate_to_json_string

    fn candidate_to_json_string(candidate :
    MutationCandidate
    , indent? : Int) -> String

    command_failure

    fn command_failure(phase :
    CommandPhase
    , code : Int, stdout? : String, stderr? : String) ->
    CommandResult

    command_phase_label

    fn command_phase_label(phase :
    CommandPhase
    ) -> String

    config_to_json_string

    fn config_to_json_string(config :
    MutestConfig
    , indent? : Int) -> String

    default_commands

    diagnose_mutant_result

    fn diagnose_mutant_result(result :
    ProjectMutantResult
    ) -> String

    discover

    fn discover(source : String, file? : String) -> Array[
    MutationCandidate
    ]

    discover_filtered

    discover_with_profile

    discover_with_rules

    edit_for_project_mutation

    estimate_execution_timeout_ms

    fn estimate_execution_timeout_ms(execution :
    MutationExecution
    ) -> Int

    execution_by_mutation_id

    experimental_rules

    Strong operator replacements intended for a small, opt-in experimental run.

    file_risk_level

    fn file_risk_level(file :
    FileRunReport
    ) -> String

    format_baseline_report

    fn format_baseline_report(report :
    BaselineReport
    ) -> String

    format_batch_plan

    fn format_batch_plan(plan :
    BatchPlan
    ) -> String

    format_batch_strategy

    fn format_batch_strategy(strategy :
    BatchStrategy
    ) -> String

    format_candidate

    fn format_candidate(candidate :
    MutationCandidate
    ) -> String

    format_candidate_list

    fn format_candidate_list(candidates : ArrayView[
    MutationCandidate
    ]) -> String

    format_config

    fn format_config(config :
    MutestConfig
    ) -> String

    format_execution_plan

    fn format_execution_plan(plan :
    ExecutionPlan
    ) -> String

    format_html_run_report

    fn format_html_run_report(report :
    ProjectRunReport
    ) -> String

    format_manifest

    fn format_manifest(source : String, file? : String) -> String

    format_manifest_json

    fn format_manifest_json(source : String, file? : String) -> String

    format_manifest_json_with_profile

    fn format_manifest_json_with_profile(source : String, profile :
    RuleProfile
    , file? : String) -> String

    format_manifest_with_profile

    fn format_manifest_with_profile(source : String, profile :
    RuleProfile
    , file? : String) -> String

    format_markdown_project_plan

    fn format_markdown_project_plan(plan :
    ProjectMutationPlan
    ) -> String

    format_markdown_run_report

    fn format_markdown_run_report(report :
    ProjectRunReport
    ) -> String

    format_mutant_patch

    fn format_mutant_patch(mutant :
    Mutant
    ) -> String

    format_project_mutation

    fn format_project_mutation(mutation :
    ProjectMutation
    ) -> String

    format_project_plan

    format_project_plan_summary

    fn format_project_plan_summary(plan :
    ProjectMutationPlan
    ) -> String

    format_quality_gate

    fn format_quality_gate(gate :
    QualityGate
    ) -> String

    format_quality_gate_report

    fn format_quality_gate_report(report :
    QualityGateReport
    ) -> String

    format_result

    fn format_result(result :
    MutantResult
    ) -> String

    format_rule_catalog

    fn format_rule_catalog(profile? :
    RuleProfile
    ) -> String

    format_run_report

    fn format_run_report(report :
    MutationRunReport
    ) -> String

    format_runner_command

    fn format_runner_command(command :
    RunnerCommand
    ) -> String

    format_runner_script

    fn format_runner_script(script :
    RunnerScript
    ) -> String

    format_selection_mode

    fn format_selection_mode(mode :
    SelectionMode
    ) -> String

    format_selection_report

    fn format_selection_report(report :
    SelectionReport
    ) -> String

    format_summary

    fn format_summary(summary :
    MutationSummary
    ) -> String

    format_survived_diagnostics

    fn format_survived_diagnostics(report :
    ProjectRunReport
    ) -> String

    format_unified_diff

    fn format_unified_diff(file : String, line : Int, before : String, after : String) -> String

    format_workspace_file_category

    fn format_workspace_file_category(category :
    WorkspaceFileCategory
    ) -> String

    format_workspace_selection_report

    fn format_workspace_selection_report(report :
    WorkspaceSelectionReport
    ) -> String

    generate_mutants

    fn generate_mutants(source : String, file? : String) -> Array[
    Mutant
    ]

    manifest

    fn manifest(source : String, file? : String) ->
    MutationManifest

    manifest_to_json_string

    fn manifest_to_json_string(manifest :
    MutationManifest
    , indent? : Int) -> String

    manifest_with_profile

    fn manifest_with_profile(source : String, profile :
    RuleProfile
    , file? : String) ->
    MutationManifest

    mutant_by_id

    fn mutant_by_id(source : String, id : Int, file? : String) ->
    Mutant
    ?

    mutation_kind_label

    fn mutation_kind_label(kind :
    MutationKind
    ) -> String

    mutation_risk_level

    fn mutation_risk_level(report :
    ProjectRunReport
    ) -> String

    mutation_risk_summary

    fn mutation_risk_summary(report :
    ProjectRunReport
    ) -> String

    outcome_label

    fn outcome_label(outcome :
    MutantOutcome
    ) -> String

    parse_config_json

    fn parse_config_json(text : String) ->
    MutestConfig
    raise

    parse_rule_profile

    fn parse_rule_profile(text : String) ->
    RuleProfile
    ?

    preview_candidate_patch

    preview_project_mutation_patch

    profile_label

    fn profile_label(profile :
    RuleProfile
    ) -> String

    quality_gate_status_label

    fn quality_gate_status_label(status :
    QualityGateStatus
    ) -> String

    report_format_label

    fn report_format_label(format :
    ReportFormat
    ) -> String

    select_changed_workspace_files

    Keep only already-selected workspace files whose root-relative paths are present in a Git changed-file list.

    shell_dialect_label

    fn shell_dialect_label(dialect :
    ShellDialect
    ) -> String

    source_file

    fn source_file(path : String, content : String) ->
    SourceFile

    summarize

    fn summarize(source : String, file? : String) ->
    MutationSummary

    validate_edit

    fn validate_edit(source : String, edit :
    TextEdit
    ) -> Bool

    Source Files