martinotten/henitai
GitHub: martinotten/henitai
Henitai 是一个 Ruby 变异测试框架,通过对源码进行系统性变异来评估测试套件是否真正覆盖了代码行为。
Stars: 2 | Forks: 0
# hen’itai
変異体 (へんいたい, hen’itai)
发音: he-n-i-ta-i (4 个音拍; 无重音)
含义: 突变体、变异生物或变体实体
一个 Ruby 变异测试框架
[](https://github.com/martinotten/henitai/actions/workflows/ci.yml)
[](https://badge.fury.io/rb/henitai)
## 成熟度
- 这是 alpha 阶段的软件,会有 bug
- Henitai 测试自身及其他项目
- 某次发布可能会导致故障
- 如果你需要一个成熟的 ruby 变异测试解决方案,请查看 [https://github.com/mbj/mutant](Mutant gem)
## 什么是变异测试?
变异测试回答了代码覆盖率无法回答的问题:**你的测试套件是否真正验证了代码的行为?**
变异测试工具会对你的源代码进行微小、系统性的更改 —— *变异体(mutants)* —— (例如将 `>` 替换为 `>=`,移除 `return` 语句,翻转布尔值),然后运行你的测试。导致至少一个测试失败的变异体被称为*被杀死(killed)*。如果通过了所有测试,则该变异体*存活(survived)* —— 这证明你的测试没有覆盖该行为。
被杀死的变异体与总变异体的比率即为**变异分数(Mutation Score, MS)**。高变异分数是一个强有力的质量信号。
由于变异测试会修改你的代码,你可以借此确保测试不会产生任何意外的副作用。
## 安装说明
添加到你的 `Gemfile` 中:
```
gem "henitai", group: :development
```
或者全局安装:
```
gem install henitai
```
**需要 Ruby 4.0.0+**
## 快速开始
```
# 在整个项目上运行 mutation testing
bundle exec henitai run
# 仅在自 main 以来更改的 subject 上运行(CI 友好)
bundle exec henitai run --since origin/main
# 在特定的 subject pattern 上运行
bundle exec henitai run 'MyClass#my_method'
bundle exec henitai run 'MyNamespace*'
# 仅重新运行先前 mutation report 中的 survivors
bundle exec henitai run --survivors-from reports/mutation-report.json
```
配置文件位于 `.henitai.yml`:
```
# yaml-language-server: $schema=./assets/schema/henitai.schema.json
integration:
name: rspec
includes:
- lib
excludes:
- lib/henitai/eager_load.rb # standalone entry points with no in-process coverage
mutation:
operators: light # light | full
timeout: 10.0
max_flaky_retries: 3
sampling:
ratio: 0.05
strategy: stratified
reports_dir: reports
thresholds:
high: 80
low: 60
```
Henitai 会在遇到未知的配置项时发出警告,并在值无效时抛出 `Henitai::ConfigurationError`
而中止。
CLI 参数会覆盖 `.henitai.yml` 中的相应值。
在变异测试开始之前,Henitai 会检查当前的覆盖率数据是否覆盖了已配置的源文件。
如果没有,Henitai 将运行配置的测试套件一次,以引导生成可用的覆盖率基线。如果
当前源代码的覆盖率仍然不可用,`henitai run` 将抛出 `Henitai::CoverageError`
并中止。
存活的变异体在被归类为存活者之前,最多会重试 `mutation.max_flaky_retries` 次。
默认重试预算为 3。
按测试覆盖率报告目前通过 RSpec 子运行器连接。Minitest 集成重用了相同的选择和
执行流程,但尚未启用按测试覆盖率格式化器。
Henitai 目前默认采用线性变异体执行方式。在 `.henitai.yml` 中设置 `jobs`
或传递 `--jobs N` 即可选择并行变异体执行。
每个派生的测试子进程都会看到一个包含稳定 worker 插槽索引的 `HENITAI_WORKER_SLOT` 环境变量
(`0..jobs-1`;在线性路径下始终为 `0`)。不稳定的重试重启会保留原始尝试的
值。接触共享外部资源的测试套件可以在无需任何 henitai 侧钩子的情况下按插槽进行
隔离 —— 例如按 worker 划分的数据库:
```
database: "myapp_test_#{ENV.fetch('HENITAI_WORKER_SLOT', '0')}"
```
默认情况下,Henitai 不会将子测试的输出显示在实时终端中。每次
基线或变异体运行都会将捕获的 stdout/stderr 写入 `reports/mutation-logs/`,
而终端仅显示进度和简要摘要。传递 `--all-logs`(或 `--verbose`)
可打印出所有捕获的子进程日志。
`henitai version` 会打印已安装的版本。当变异分数达到低阈值时,`henitai run` 会以 `0`
退出,如果未达到则以 `1` 退出,遇到框架错误则以 `2` 退出。
`henitai run --incremental` 会从历史存储(`reports/mutation-history.sqlite3`)中复用仍然有效的 `Killed` 判定,
而不是重新执行它们:仅当主体的源代码和每个覆盖该主体的测试文件
与记录的内容在字节上完全一致时,才会复用判定。复用的变异体在报告中依然
可见(`stableId` 旁边会显示 `fromCache: true`),并计入
MS/MSI;终端会打印 `N of M verdicts reused from history`(N 个判定从历史记录中复用,共 M 个)。
存活者、超时和错误总是会重新执行。`--force` 会跳过复用。
`henitai run --dry-run` 会列出过滤后的变异体集合,而不执行任何
测试,且始终以 `0` 退出。
`henitai run --survivors-from ...` 会执行部分重跑:它仅报告
选定的存活者,跳过基于阈值的退出检查,并且不会更新
运行趋势历史记录。工作区(worktree)中的未提交更改也会被包含在内,因此你可以在本地编辑
测试而无需先提交;如果 `includes` 下的源文件被修改过,Henitai 将保守地重新运行匹配的
存活者。
### 内联跳过变异
`# henitai:disable` 魔法注释可以在调用处将代码排除在变异之外,
而无需修改 `.henitai.yml`:
```
def risky_calc(x)
x * 2 # henitai:disable — skips mutants on this line
end
# henitai:disable — 跳过此方法中的每个 mutant
def legacy_shim(x)
x - 1
end
```
行级形式适用于从该行开始的变异体。方法级形式是
紧挨着 `def` 上方连续注释块中的独立注释(空行会中断这种关联)。允许使用尾随文本
(`# henitai:disable -- reviewed defensive branch`)。被跳过的变异体会被
报告为 `Ignored`,因此它们会在报告中保持可见,而不是悄无声息地
消失。请使用 `mutation.ignore_patterns` 来设定仓库范围的策略,并使用
`# henitai:disable` 进行一次性的、经过审查的排除。
指令还可以针对特定的操作符,附带原因(序列化为
`statusReason`,在 HTML 报告中可见),并覆盖特定区域:
```
x = a + b # henitai:disable ArithmeticOperator — only this operator
x = a + b # henitai:disable ArithmeticOperator: log noise — with a reason
# henitai:disable RegexMutator: timing-sensitive matcher
def parse(line)
...
end
# henitai:disable-start ConditionalExpression
...region...
# henitai:disable-end
```
操作符名称必须与 `henitai operator list` 中的规范名称完全匹配;
未知的名称,或不匹配/嵌套的 `disable-start`/`disable-end` 指令会导致运行中止(退出代码 `2`),并
指出出错的文件和行。区域不能嵌套。
该仓库包含一个 JSON Schema:[`assets/schema/henitai.schema.json`](/workspaces/henitai/assets/schema/henitai.schema.json),用于编辑器自动补全。
## 操作符集合
**Light**(默认) —— 高信号、低噪声的操作符,涵盖了大多数真实环境中的缺陷:
- `ArithmeticOperator` —— `+` ↔ `-`, `*` ↔ `/`
- `EqualityOperator` —— `==` ↔ `!=`, `>` ↔ `<` 等(仅限关系运算符)
- `LogicalOperator` —— `&&` ↔ `||`
- `BooleanLiteral` —— `true` ↔ `false`, `!expr`
- `ConditionalExpression` —— 移除分支体
- `StringLiteral` —— 空字符串替换
- `ReturnValue` —— 变异返回表达式
**Full** —— 增加了较低信号的操作符:
- `ArrayDeclaration`, `HashLiteral`, `RangeLiteral`
- `MethodChainUnwrap` —— 移除方法链中的一个环节
- `RegexMutator` —— 变异正则表达式量词、锚点、字符类取反
- `SafeNavigation` —— `&.` → `.`
- `PatternMatch` —— case/in 分支移除
- `BlockStatement` —— 移除块
- `MethodExpression` —— 移除调用
- `AssignmentExpression` —— 变异复合赋值
- `UnaryOperator` —— 移除一元 `-` 和 `~`
- `UpdateOperator` —— 交换复合赋值(`+=`↔`-=`, `*=`↔`/=`, `||=`↔`&&=`)
- `EqualityIdentityOperator` —— `==` ↔ `eql?`/`equal?`(最难被杀死的相等性配对;参见 [ADR-10](docs/architecture/adr/ADR-10-split-equality-identity-mutations.md))
## Stryker Dashboard 集成
```
# .henitai.yml
reporters:
- terminal
- html
- json
- dashboard
dashboard:
project: "github.com/your-org/your-repo"
base_url: "https://dashboard.stryker-mutator.io"
```
在你的 CI 环境中设置 `STRYKER_DASHBOARD_API_KEY` 以发布报告。当
key、project 和 version 都存在时,`dashboard` 报告器会将
Stryker-schema 报告上传到 dashboard REST API(默认为
`https://dashboard.stryker-mutator.io`);否则将静默跳过。
project 默认为 git 远程仓库,version 默认为当前分支或 CI
引用(`GITHUB_REF_NAME`/`GITHUB_REF`/`GITHUB_SHA`)。
默认情况下,JSON 报告会写入 `reports/mutation-report.json`。设置
`reports_dir` 可更改输出目录。
## 开发说明
```
git clone https://github.com/martinotten/henitai
cd henitai
bundle install
bundle exec rspec # run tests
bundle exec rake smoke:integration:all # run rspec/minitest integration smoke projects
bundle exec rubocop # lint
bundle exec henitai clean # remove stale generated report artifacts
bundle exec henitai run # dogfood
```
框架集成冒烟测试项目位于 `spec/fixtures/integration_smoke/` 下,
并通过本地路径依赖针对小型 RSpec 和 Minitest 应用执行 `henitai`。
Git 钩子支持记录在 [`.githooks/pre-commit`](/Users/martinotten/projects/mo/henitai/.githooks/pre-commit) 中。
使用 `git config core.hooksPath .githooks` 启用它,这样在创建提交之前就会运行 RuboCop、
RSpec 和集成冒烟测试套件。
包含了一个 Dev Container 配置(`.devcontainer/`),适用于 VS Code,内置官方的 `ruby:4`
镜像,并预装了 Codex CLI 和 RTK。Codex 支持会在
容器创建期间通过 `rtk init -g --codex --auto-patch` 进行引导。
## 架构
查看 [`docs/architecture/architecture.md`](docs/architecture/architecture.md) 获取完整设计文档,包括:
- Phase-Gate 流水线(5 个阶段)
- 基于 AST 的操作符实现
- Fork 隔离模型
- Stryker JSON schema 集成
- [`docs/architecture/adr/`](docs/architecture/adr/) 中的架构决策
- 三阶段路线图
## 许可证
[MIT 许可证](LICENSE) — © 2026 Martin Otten
标签:Ruby, SOC Prime, 变异测试, 开发工具, 测试框架, 知识库