ミューテーションテスト

はじめる

XDebug 3.0以降 または PCOV が必要です。

ミューテーションテストは、コードに小さな変更(ミューテーション)を加え、テストがそれらを検出できるかどうかを確認する革新的な手法です。これにより、コードカバレッジの達成だけでなく、テストの実際の品質についても徹底的にアプリケーションをテストできます。テストスイートの弱点を特定し、品質を向上させるための優れた方法です。

ミューテーションテストを開始するには、テストファイルに移動し、`covers()` 関数または `mutates` 関数を使用して、テストがコードのどの部分をカバーしているかを具体的に指定します。

1covers(TodoController::class); // or mutates(TodoController::class);
2 
3it('list todos', function () {
4 $this->getJson('/todos')->assertStatus(200);
5});

`covers`関数と`mutates`関数は、ミューテーションテストに関しては同一です。ただし、`covers`はコードカバレッジレポートにも影響を与えます。指定された場合、参照されたコード部分からの実行されたコードのみを含むようにコードカバレッジレポートをフィルタリングします。

次に、`--mutate`オプションを付けてPest PHPを実行して、ミューテーションテストを開始します。処理速度を上げるために`--parallel`オプションを使用することをお勧めします。

1./vendor/bin/pest --mutate
2# or in parallel...
3./vendor/bin/pest --mutate --parallel

Pestは、「ミューテーションされた」コードに対してテストを再実行し、テストがまだパスしているかどうかを確認します。ミューテーションに対してテストがまだパスしている場合、そのテストはコードのその特定の部分をカバーしていないことを意味します。その結果、Pestはミューテーションとコードの差分を出力します。

1UNTESTED app/Http/TodoController.php > Line 44: ReturnValue - ID: 76d17ad63bb7c307
2 
3class TodoController {
4 public function index(): array
5 {
6 // pest detected that this code is untested because
7 // the test is not covering the return value
8- return Todo::all()->toArray();
9+ return [];
10 }
11}
12 
13 Mutations: 1 untested
14 Score: 33.44%

未テストのコードを特定したら、それをカバーする追加のテストを作成できます。

1covers(TodoController::class);
2 
3it('list todos', function () {
4+ Todo::factory()->create(['name' => 'Buy milk']);
5 
6- $this->getJson('/todos')->assertStatus(200);
7+ $this->getJson('/todos')->assertStatus(200)->assertJson([['name' => 'Buy milk']]);
8});

その後、`--mutate`オプションを付けてPestを再実行し、ミューテーションが「テスト済み」でカバーされているかどうかを確認できます。

1Mutations: 1 tested
2Score: 100.00%

ミューテーションスコアが高いほど、テストスイートの品質が高くなります。100%のミューテーションスコアは、すべてのミューテーションが「テスト済み」であることを意味し、ミューテーションテストの目標です。

「未テスト」または「未カバー」のミューテーションが表示された場合、またはミューテーションスコアが100%未満の場合、通常は**テストが不足している**か、**テストがすべてのエッジケースをカバーしていない**ことを意味します。

私たちのプラグインはPest PHPに深く統合されています。そのため、ミューテーションが導入されるたびに、Pest PHPは

  • ミューテーションされたコードをカバーするテストのみを実行して処理速度を上げます。
  • できるだけ多くのキャッシュを使用して、後続の実行での処理速度を上げます。
  • 有効になっている場合、複数のテストを並列に実行する並列実行を使用して処理速度を上げます。

テスト済みのミューテーションと未テストのミューテーション

ミューテーションテストを実行すると、「主に」2種類のミューテーションが表示されます。**テスト済み**ミューテーションと**未テスト**ミューテーションです。

  • **テスト済みミューテーション**: これらは、テストスイートによって検出されたミューテーションです。ミューテーションによって導入された変更をテストが検出できたため、「テスト済み」と見なされます。

たとえば、次のミューテーションは、テストスイートが変更を検出できたため、「テスト済み」と見なされます。

1class TodoController
2{
3 public function index(): array
4 {
5- return Todo::all()->toArray();
6+ return [];
7 }
8}
9 
10it('list todos', function () {
11 Todo::factory()->create(['name' => 'Buy milk']);
12 
13 // this fails because the mutation changed the return value, proving that the test is working and testing the return value...
14 $this->getJson('/todos')->assertStatus(200)->assertJsonContains([
15 ['name' => 'Buy milk'],
16 ]);
17});
  • **未テストミューテーション**: これらは、テストスイートによって検出されなかったミューテーションです。ミューテーションによって導入された変更をテストが検出できなかったため、「未テスト」と見なされます。

たとえば、次のミューテーションは、テストスイートが変更を検出できなかったため、「未テスト」と見なされます。

1class TodoController
2{
3 public function index(): array
4 {
5- return Todo::all()->toArray();
6+ return [];
7 }
8}
9 
10it('list todos', function () {
11 Todo::factory()->create(['name' => 'Buy milk']);
12 
13 // this test still passes even though the return value was changed by the mutation...
14 $this->getJson('/todos')->assertStatus(200);
15});

戻り値の変更は、多くの可能なミューテーションの1つにすぎません。通常、ミューテーションは、戻り値の変更、メソッド呼び出しの変更、メソッド引数の変更などです。

最小閾値の適用

包括的なテストを確保し、テストの品質を維持するには、ミューテーションテストの結果に最小閾値を設定することが重要です。Pestでは、`--mutation`オプションと`--min`オプションを使用して、ミューテーションテストスコアの最小閾値を定義できます。指定された閾値を満たしていない場合、Pestはエラーを報告します。

1./vendor/bin/pest --mutate --min=40

オプションと修飾子

ミューテーションテストの実行時には、次のオプションと修飾子を使用できます。

@pest-mutate-ignore

ミューテーションの生成時に、指定されたコード行を無視します。

1public function rules(): array
2{
3 return [
4 'name' => 'required',
5 'email' => 'required|email', // @pest-mutate-ignore
6 ];
7}

--id

指定されたIDを持つミューテーションのみを実行します。元のランと同じオプションを指定する必要があります。

1./vendor/bin/pest --mutate --id=ecb35ab30ffd3491

--everything

`covers()`メソッドをバイパスして、プロジェクトのすべてのクラスに対してミューテーションを生成します。このオプションは非常にリソースを消費するため、`--covered-only`オプションと組み合わせて使用​​する必要があります。

1./vendor/bin/pest --everything --parallel --covered-only

処理速度を上げるために`--parallel`オプションを組み合わせることもお勧めします。

--covered-only

テストによってカバーされているコード行でのみミューテーションを生成します。

1./vendor/bin/pest --mutate --covered-only

--bail

最初の未テストまたは未カバーのミューテーションでミューテーションテストの実行を停止します。

1./vendor/bin/pest --mutate --bail

--class

指定されたクラスに対してミューテーションを生成します。例:`--class=App\Models`。

1 
2./vendor/bin/pest --mutate --class=App\Models

--ignore

ミューテーションの生成時に、指定されたクラスを無視します。例:`--ignore=App\Http\Requests`。

1./vendor/bin/pest --mutate --ignore=App\Http\Requests

--clear-cache

ミューテーションキャッシュをクリアし、ミューテーションテストを最初から実行します。

1./vendor/bin/pest --mutate --clear-cache

--no-cache

キャッシュされたミューテーションを使用せずにミューテーションテストを実行します。

1./vendor/bin/pest --mutate --no-cache

--ignore-min-score-on-zero-mutations

ミューテーションがない場合、最小スコア要件を無視します。

1./vendor/bin/pest --mutate --min=80 --ignore-min-score-on-zero-mutations

--profile

標準出力に、最も遅いミューテーション上位10件を出力します。

1./vendor/bin/pest --mutate --profile

--retry

未テストまたは未カバーのミューテーションを最初に実行し、最初のエラーまたは失敗で実行を停止します。

1./vendor/bin/pest --mutate --retry

--stop-on-uncovered

最初の未テストのミューテーションでミューテーションテストの実行を停止します。

1./vendor/bin/pest --mutate --stop-on-uncovered

--stop-on-untested

最初の未テストのミューテーションでミューテーションテストの実行を停止します。

1./vendor/bin/pest --mutate --stop-on-untested

Pest PHPのミューテーションテスト機能は、テストスイートの品質を向上させる強力なツールであることがわかります。次の章では、スナップショットを使用してコードをテストする方法について説明します。スナップショットテスト