https://a.storyblok.com/f/270183/1368x665/24a2391523/26mar_dev-blog_php-pest-mut-test.jpg

PEST変異テストでPHPコードを強化しよう

最終更新日 March 3, 2026

所要時間:1 分

PESTのデファクトテストライブラリになったので、特に多くの採用がありました。 Laravelユーザーにとって事実上のテストライブラリとなったため、Laravel開発者は PHPUnitとPESTのどちらかを選択できるようになりました。選択肢が増えることは良いことですが、採用率が上がるということは、開発ライフサイクルに多くの 新機能が出荷されていることを意味します。

以来、テストの世界ではたいしたことが起こっていなかったことを考えると、突然変異テストが流行した速度は驚くべきものだった。 ドメイン駆動設計そして 行動駆動開発.私は、BDDが自動的に解析するデモに圧倒されたことを覚えている。 ガーキンファイルを自動的に解析し、そこからテストを吐き出す シアラン・マクナルティによる PHPロンドンでCiaran McNultyが教えてくれた。このようなことはプロとしてやったことはありませんでしたが、PHP開発者の間で InfectionPHPの人気に続いて、突然変異テストがPHP開発者たちに採用され始めたとき、私はこのテストに高い価値を見出すだろうと思っていた。

そして、突然変異検査がより一般的になった、 ヌーノ・マドゥロはPEST 3.0に変異テストを搭載すると発表した。この記事では、Vonage Messaging APIを使ったテスト例で、このテストに飛び込んでいこうと思う。 Vonage Messages API.

突然変異検査とは何か?

まず最初に言っておきたいのは、コードを可能な限り堅牢にするために追加できるツールは、常にプラスになるということだ。そのため、以下のような静的解析のようなツールも必要不可欠なもののリストに加えることができます。 PsalmPHPPHPStanphpcsfixerそしてテスト駆動開発による堅牢なテストです。

私たちは人間なので、テストでミスを犯すことがある(私もそうだ)。テストを書いている途中で、アサーションがなく、例外を投げずにコードが実行されたことをテストしたいだけだとする。

public function testService()
{
	$service = new \Vonage\ServiceContainer('dev');
	$service->runService();

	// If service fails, exception, so dummy completion assertion
	$this->assertTrue(true);
}

そこにあるのは偽陽性だ。確かに、あなたはその背後にある理由を見ることができる。

100% Line Test Coverage

技術的には正しいが、それは本当のテストではない。もし基礎となるコードが変更され、文書化されていない例外がスローされなくなったら?

突然変異テストが行うのは、基礎となるコードを一度に1スレッドずつ変更することである(私たちはこれらのスレッドを「ミュータント」と呼んでいる)。もしコードを変更し始めたら、技術的にはすべてのテストが失敗するはずだ。私たちはすでに、そうならないかもしれないという人間の要素を導入している。我々が求めている最終的な結果は、多くの変異体が作られたが、そのすべてが「殺された」、つまり変異のためにテストが失敗したということである。もし「生き残る」突然変異体がいれば、テストはまだパスしていることになる。もしまだ合格しているのなら、テストはあなたのコードの変更に気づいていないということです。それはよくない。

ベースラインVonage PHP SDK テスト

まず、Vonageを使ってRCSメッセージを送信する架空のLaravelサービスを使って、通常のテストを実装する方法を紹介しよう。

<?php

namespace App\Services;

use Vonage\Client;

use Vonage\Messages\Channel\RCS\RcsText;

class RcsService
{

   public function __construct(
       private Client $vonage
   ) {}

   public function send(string $to, string $message): array
   {
       $response = $this->vonage->messages()->send(
           new RcsText($to, 'VonageApp', $message)
       );

       return $response;
   }
}

簡単にするために、Laravelのサービスコンテナは、事前に設定されたVonage Clientオブジェクトを注入するとします。では、実際にAPIコールを行わないテストが必要です。これにはモックを使います。

use App\Services\RcsService;
use Vonage\Client;
use Vonage\Messages\Channel\RCS\RcsText;
use Vonage\Messages\Client as MessagesClient;

beforeEach(function () {
   $this->messagesClient = Mockery::mock(MessagesClient::class);
   $this->vonage = Mockery::mock(Client::class);
   $this->vonage->shouldReceive('messages')->andReturn($this->messagesClient);
});

afterEach(function () {
   Mockery::close();
});

test('Will send message using Vonage SDK to end number', function () {
   $response = ['message_uuid' => 'abc-123', 'to' => '33600000000'];

   $this->messagesClient
       ->shouldReceive('send')
       ->once()
       ->with(Mockery::type(RcsText::class))
       ->andReturn($response);

   $service = new RcsService($this->vonage);
   $result = $service->send('+33600000000', 'Hello world');
   expect($result)->toBeArray();
});

よし、テストは合格だ。よかった。しかし、コードを変異させたらどうなるだろうか?

突然変異テストはPEST 3に組み込まれているので、今すぐこの上で突然変異を実行し、何が起こるかを見ることができる。PESTにはたくさんの設定オプションがありますが、突然変異の発生を指示する最も簡単な方法は ヘルパーメソッドヘルパー・メソッドを使うことだ。この場合、以下の行をテスト・ファイルに追加する:

use App\Services\RcsService;

covers(RcsService::class);

これはPESTに、このテストファイルを実行するときに、先に進んで RcsService.PESTの変異を100%の合格しきい値で実行する:

./vendor/bin/pest --mutate --min=100

次のような結果が得られる:

 Mutating application files...

  1 Mutations for 1 Files created

   RUN  app/Services/RcsService.php

  ⨯ Line 20: AlwaysReturnEmptyArray

  ---------------------------------------------------------------------------------------------------------------------------  

   UNTESTED  app/Services/RcsService.php  > Line 20: AlwaysReturnEmptyArray - ID: 5befe24d3b8d7f6f

       public function send(string $to, string $message): array

       {

           $response = $this->vonage->messages()->send(new RcsText($to, 'VonageApp', $message));

  -        return $response;

  +        return [];

       }

   }

  

  Mutations: 1 untested, 0 tested

  Score:     0.00%

  Duration:  0.30s

   FAIL  Mutation score below expected: 0.0 %. Minimum: 100.0 %.

ああ、何かがおかしい。でも何が?嗚呼!見よ、突然変異検査の驚くべき威力を!次のステップで説明しよう:

  • PESTは RcsService()常に空の配列を返す

  • PEST はテストファイルをミュータントとして実行する。

  • ミュータントに失敗してほしい 失敗する、 そして 殺される

  • そんなことはない。

  • つまり テストが弱すぎる

では、なぜそんなに弱いのか?結論から言うと、Vonageから戻ってくるペイロードをテストすべきなのだ。このVonage APIが実際には空白の配列を返さないという事実はさておき、返されるペイロードをテストすべきなのだ。を使って ->toBeArray()を使うだけでは不十分です。

弱点を修正する

このテストでは実際にモック出力を定義しているので、ここでテストしているのは、返されたペイロード(我々が提供したもの)が何ら変更されていないことだと考えていいだろう。と定義しているので $responseを定義したので、そのデータ構造と正確に一致するようにテストを変更しなければなりません。

expect($result)->toBe($response);

それだけだ。たった1行、アサーションを変えただけだ。PESTの突然変異をもう一度実行すると、次のようになる:

Tests:    1 passed (2 assertions)

  Duration: 0.26s

  Mutating application files...

  1 Mutations for 1 Files created

   RUN  app/Services/RcsService.php

  ✓ Line 20: AlwaysReturnEmptyArray

  Mutations: 1 tested

  Score:     100.00%

  Duration:  0.34s

すごいね。

結論

出発点としては、これはかなり基本的な例であることは承知している。しかし、突然変異テストが提供するコンセプトが、ここで取り上げる主な内容です。これはほんの数行のコードですが、あなたのコードベースを強固にするために、 突然変異テストがいかに大きな力を持つかを示しています。PHPStanのようなものと一緒に使えば、PHP開発者はこれ以上ないほど堅牢なコードを出荷できるようになります。

ご質問がある場合、またはあなたが作っているものを共有したい場合は、こちらをクリックしてください。

最新の開発者向けニュース、ヒント、イベント情報をお届けします。

シェア:

https://a.storyblok.com/f/270183/400x385/12b3020c69/james-seconde.png
James SecondeシニアPHPデベロッパー

スタンダップ・コメディーの学位論文を持つ俳優の訓練を受け、ミートアップ・シーンを経てPHP開発に携わるようになった。技術について話したり書いたり、レコード・コレクションから変わったレコードを再生したり買ったりしています。