突然変異テストとは
テストの品質を測るテスト
実装の一部分に変更を加えて単体テストを実行し、各テストが落ちることを確認する。
テストの品質が高い場合、実装の一部が変わればテストは落ちる。しかし、テストの品質が低い場合、実装の一部が変わってもテストが通る。
本項では 突然変異テストと テストライブラリ Stryker について記載している
Stryker
突然変異テストのライブラリ
2026/4/25 現在はJavaScript, C#, Scalaをサポートしている
1. 準備
1-1. インストール
@stryker-mutator/coreと テストランナー用のライブラリ をインストールする
今回はvitestを使用しているため、テストランナー用のライブラリは @stryker-mutator/vitest-runner をインストールする(参考 : Stryker / Vitest Runner)
$ npm install -D @stryker-mutator/core @stryker-mutator/vitest-runner
※Jest など他テストランナーもサポートしているため、Strykerの各テストランナーのページを参照すると良い
1-2. 初期設定とコンフィグファイルの作成
npx init strykerで対話形式で初期設定を行う(参考 : Stryker / getting-started)
対話が完了するとプロジェクトルートにstryker.config.jsonが作成される※
必要に応じていくつか設定を変更する
{
"testRunner": "vitest",
"vitest": {
"configFile": "vitest.config.ts",
"related": true
}
}
※1 コンフィグファイルの形式はjsなどもサポートされている。Strykerのドキュメントの各コンフィグ設定が JSON ベースのため、jsonで作成。
※2 "vitest"."dir": "packages"は不要のため削除している
※3 コンフィグファイルのみ手作業で追加しても支障はない
1-3. .gitignoreに追記
突然変異テストの実行結果がreportsフォルダに作成されるため、追跡対象外とする
reports
2. 実施
2-1. 実装と単体テストの作成
-
100以上の数値のみ抽出するメソッドと単体テストを作る
テストライブラリは vitest を使用実装(バグあり)
export function extractOverHundred(arr: ReadonlyArray<number>) { return arr.filter((a) => a > 100); // バグ:>=が正しい }テスト(境界値不足)
import { extractOverHundred } from './common'; it('100以上の数値のみ抽出する', () => { // 検証に「100」が含まれないため、バグに気づかない const arr = [99, 101, 1000]; expect(extractOverHundred(arr)).toStrictEqual([101, 1000]); });
$ npx vitest run
unit test tests/common.test.ts (1 test) 4ms
✓ extractOverHundred (1)
✓ 100以上の数値のみ抽出する 2ms
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 21:45:55
Duration 234ms (transform 27ms, setup 0ms, import 45ms, tests 4ms, environment 0ms)
- 検証内容に誤りはないためテストは通る
2-2. Strykerの実行
-
npx stryker runで突然変異テストを実行する
実装に変異を加えたファイルを作成し、そのファイルに対して単体テストを実行してくれる全出力
$ npx stryker run > stryker run 21:53:13 (3368) INFO ProjectReader Found 2 of 20 file(s) to be mutated. 21:53:13 (3368) INFO Instrumenter Instrumented 2 source file(s) with 7 mutant(s) 21:53:13 (3368) INFO ConcurrencyTokenProvider Creating 11 test runner process(es). 21:53:15 (3368) INFO DryRunExecutor Starting initial test run (vitest test runner with "perTest" coverage analysis). This may take a while. 21:53:15 (3368) INFO DryRunExecutor Initial test run succeeded. Ran 1 tests in 0 seconds (net 3.4864000000000033 ms, overhead 297.5136 ms). Mutation testing [==================================================] 100% (elapsed: <1m, remaining: n/a) 7/7 Mutants tested (1 survived, 0 timed out) All tests/common.test.ts ✓ extractOverHundred 100以上の数値のみ抽出する (killed 6) [Survived] EqualityOperator src/common/common.ts:9:28 - return arr.filter((a) => a > 100); + return arr.filter((a) => a >= 100); Tests ran: extractOverHundred 100以上の数値のみ抽出する Ran 1.00 tests per mutant on average. -----------|------------------|----------|-----------|------------|----------|----------| | % Mutation score | | | | | | File | total | covered | # killed | # timeout | # survived | # no cov | # errors | -----------|--------|---------|----------|-----------|------------|----------|----------| All files | 85.71 | 85.71 | 6 | 0 | 1 | 0 | 0 | common.ts | 85.71 | 85.71 | 6 | 0 | 1 | 0 | 0 | -----------|--------|---------|----------|-----------|------------|----------|----------|
[Survived] EqualityOperator
src/common/common.ts:9:28
- return arr.filter((a) => a > 100);
+ return arr.filter((a) => a >= 100);
Tests ran:
extractOverHundred 100以上の数値のみ抽出する
-
[Survived]が実装に変更を加えてもテストが成功した記録 - 「実装の
>を>=に変異させたとき、先ほどのテストが通った」と出力されている
3. 片付け
突然変異テスト実行時に作成されるファイルは不要のため削除する
$ rm -r .stryker-tmp
※ 1-2. 初期設定とコンフィグファイルの作成 で.gitignoreに本フォルダ名が追記されているため、追跡対象になることはない
所管
- 境界値テストなどテスト作成時に考慮すべき観点が不足していないか自動で確認できるだけでも、突然変異テストは非常に有用であると感じた