Henka
変化(henka)。 テストのためのテストです。Henka はコードをわざと小さく 1 か所ずつ壊し、そのたびにテストを実行して、どのテストも捕まえられなかった変更を一覧にします。カバレッジがわかるのは、その行が実行されたかどうかだけです。Henka は、その行の動きにテストが本当に頼っているかを確かめます。
実行する
Vitest 5 以降でテストしているプロジェクトで実行します。
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 < 3 → hits <= 3
✖ missed src/unused.ts:1:40 n * 2 → n / 2
missed: no test failed, so nothing tests what these change. Add a test of the behaviour they control.
src/score.ts:2:12 hits < 3 → hits <= 3
src/unused.ts:1:40 n * 2 → n / 2: no test loads this filehits < 3 → hits <= 3 が missed になったのは、2 つの式の結果が分かれる唯一の値 combo(3) を呼ぶテストがないからです。そのテストを書けば解決します。missed がひとつでもあると終了コードは 1 になるので、CI のゲートに使えます。結果は mutants.out/(outcomes.json と結果ごとのテキストファイル)に書き出され、エージェントや CI から読めます。
何を変えるか
| 変更 | 例 |
|---|---|
| 算術 | + ↔ -、* ↔ /、% → *、+= ↔ -=、*= ↔ /=、++ ↔ -- |
| 比較 | < ↔ <=、> ↔ >=、=== ↔ !==、== ↔ != |
| 論理 | && ↔ ||、!x → x |
| 符号 | -x → x |
| 数値 | n → 0、0 → 1 |
| 関数の本体 | 空にするか、戻り値の型に合わせて 0、""、true、false を返す |
文字列の連結、型、import、オブジェクトのキーには手を付けません。行の上か同じ行に // henka-skip と書くとその行を飛ばし、ファイルのどこかに // henka-skip-file と書くとファイルごと飛ばします。
結果の種類
| 結果 | 意味 |
|---|---|
caught | テストが失敗しました。この変更が影響する部分はテストされています。 |
missed | すべてのテストが通りました。この変更が影響する部分を確かめるテストがありません。その振る舞いのテストを書きましょう。 |
held | 無視してよい変更で、すべてのテストが通りました。Keiko のシードの変更で使います。 |
brittle | 無視してよいはずの変更(乱数のシードなど)でテストが失敗しました。 |
unviable | 変更を適用できませんでした。 |
実行のしくみ
Henka はまず何も変えずに、対象のファイルに関係するテストを実行し、どのテストファイルがどのソースファイルを読み込むかを記録します。そのあと、ジョブごとに Vitest のプロセスを 1 つ開いたままにして、変更を 1 つずつ順に試します。変更したコードはファイルを読み込むときに Vite プラグインが差し替えるので、ディスク上のファイルは変わりません。実行するのはそのファイルを読み込むテストファイルだけで、最初の失敗で止まります。ベースラインの 5 倍(最低 20 秒)を超えた実行は止めて caught に数えます。変更でテストが止まらなくなったということだからです。
| オプション | |
|---|---|
--since <ref> | git の ref 以降に変わった行だけを対象にします。新しいファイルは全体が対象です。 |
--jobs <n> | 同時に試す変更の数(デフォルトはコア数の半分、最大 4)。 |
--timeout <s> | 実行をこの秒数で止めます。 |
--root <dir> | Vitest が設定を探す場所。 |
--out <dir> | 結果の書き出し先(デフォルトは mutants.out)。 |
--list | 変更を実行せずに一覧表示します。 |
--json | レポートを JSON で出力します。 |
Web ゲーム
npx henka には Vitest が必要です。debuff のゲームのテストは debuff test で実行され、これは Keiko の上で動きます。そのため debuff では Keiko のアダプター npx keiko mutate を使いましょう。こちらはコードではなくゲームを変えます。パラメータを範囲の両端にし、乱数のシードを変え、フレームレートを下げます。使い方をテストをテストするで説明しています。
独自のアダプター
コアはそれ自体では何も実行しません。テストの実行と変更の適用はアダプターが担うので、どのテストランナーでも使えます。
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 を通してコードを読み込むアダプターで使えます。