# Henka

**変化（henka）。** テストのためのテストです。Henka はコードをわざと小さく 1 か所ずつ壊し、そのたびにテストを実行して、どのテストも捕まえられなかった変更を一覧にします。カバレッジがわかるのは、その行が実行されたかどうかだけです。Henka は、その行の動きにテストが本当に頼っているかを確かめます。

[GitHub](https://github.com/debuffjp/debuff/tree/main/tooling/henka) · [Keiko のアダプター](/ja/keiko/replays-and-ci#mutate) · [リリース記事](/ja/blog/introducing-henka)

## なぜ必要か {#why}

debuff のエフェクト用パッケージ `@debuffjp/juice` は、行カバレッジが 94% で、テストは 9 件とも通っています。Henka はそのパーティクル処理の 1 行を次のように変えました。

```diff
- p.x += p.vx * dt;
+ p.x -= p.vx * dt;
```

これで全部のパーティクルが逆向きに飛びますが、9 件のテストはすべて通ったままです。Henka が juice に加えた 266 個の変更のうち 132 個はすべてのテストを通りました。1 つずつ読むと、そのうち 69 個はテストが実行しているコードの本当の穴でした。詳しい数字は[行カバレッジ 94% が見逃したもの](/ja/research/what-coverage-missed)にあります。

見逃しの 1 件 1 件が、どのテストも確かめていない行と変更を示します。次に書くべきテストはそこです。エージェントが書いたテストの確認にも向いています。`mutants.out/missed.txt` をエージェントに渡し、各行が担う振る舞いのテストを頼みましょう。

- 設定ファイルは不要です。Vitest でテストしているプロジェクトで `npx henka` を実行するだけです。
- ディスク上のファイルは変わりません。Vite がファイルを読み込むときに、変更したコードに差し替えます。
- 変更したファイルを読み込むテストファイルだけを実行し、最初の失敗で止めます。
- `--since main` を付けると、ブランチで変えた行だけを調べます。

## 実行する {#run}

Vitest 5 以降でテストしているプロジェクトで実行します。

```bash
npx henka                  # mutate src/
npx henka src/physics.ts   # one file or folder
npx henka --since main     # only the lines changed since main
```

```
✔ caught   src/score.ts:3:15 hits * 2 → hits / 2
✖ missed   src/score.ts:2:12 hits ` ↔ `>=`、`===` ↔ `!==`、`==` ↔ `!=` |
| 論理 | `&&` ↔ `\|\|`、`!x` → `x` |
| 符号 | `-x` → `x` |
| 数値 | `n` → `0`、`0` → `1` |
| 関数の本体 | 空にするか、戻り値の型に合わせて `0`、`""`、`true`、`false` を返す |

文字列の連結、型、import、オブジェクトのキーには手を付けません。行の上か同じ行に `// henka-skip` と書くとその行を飛ばし、ファイルのどこかに `// henka-skip-file` と書くとファイルごと飛ばします。

## 結果の種類 {#outcomes}

| 結果 | 意味 |
| --- | --- |
| `caught` | テストが失敗しました。この変更が影響する部分はテストされています。 |
| `missed` | すべてのテストが通りました。この変更が影響する部分を確かめるテストがありません。その振る舞いのテストを書きましょう。 |
| `held` | 無視してよい変更で、すべてのテストが通りました。Keiko のシードの変更で使います。 |
| `brittle` | 無視してよいはずの変更（乱数のシードなど）でテストが失敗しました。 |
| `unviable` | 変更を適用できませんでした。 |

## 見逃しへの対処 {#misses}

Henka が加えた変更そのものではなく、その行が担う振る舞いをテストしましょう。上のパーティクルの行なら、パーティクルが速度に `dt` を掛けた分だけ動くことを確かめるテストで、`-=` と `/` の両方を捕まえられます。変更後の値を固定するだけのテストでは、その 1 個しか捕まえられません。

テストからは違いが見えない変更もあります。たとえば曲線の 2 つの区間がちょうど同じ値でつながる位置での、`` | git の ref 以降に変わった行だけを対象にします。新しいファイルは全体が対象です。 |
| `--jobs ` | 同時に試す変更の数（デフォルトはコア数の半分、最大 4）。 |
| `--timeout ` | 実行をこの秒数で止めます。 |
| `--root ` | Vitest が設定を探す場所。 |
| `--out ` | 結果の書き出し先（デフォルトは `mutants.out`）。 |
| `--list` | 変更を実行せずに一覧表示します。 |
| `--json` | レポートを JSON で出力します。 |

## Web ゲーム {#games}

`npx henka` には Vitest が必要です。debuff のゲームのテストは `debuff test` で実行され、これは [Keiko](/ja/keiko/) の上で動きます。そのため debuff では Keiko のアダプター `npx keiko mutate` を使いましょう。こちらはコードではなくゲームを変えます。パラメータを範囲の両端にし、乱数のシードを変え、フレームレートを下げます。使い方を[テストをテストする](/ja/keiko/replays-and-ci#mutate)で説明しています。

## 独自のアダプター {#adapter}

コアはそれ自体では何も実行しません。テストの実行と変更の適用はアダプターが担うので、どのテストランナーでも使えます。

```ts
import { type Adapter, formatReport, mutate, writeOut } from "henka";

const adapter: Adapter = {
  name: "my-runner",
  baseline: () => runMyTests(),
  mutants: () => [{ id: "timeout=0", label: "request timeout 30 s → 0", expect: "noticed" }],
  run: (mutant) => runMyTests({ apply: mutant.id }),
};

const report = await mutate(adapter, { jobs: 4 });
writeOut(report, "mutants.out");
console.log(formatReport(report));
```

`henka/source` を使うと、任意の ESTree パーサーでソースの変更を見つけて適用できます。`henka/vite` の Vite プラグインは、Vite を通してコードを読み込むアダプターで使えます。