PHPUnitのスタブとモックの違いについて解説します。
テストダブルとは
スタブ、モックについて解説する前にテストダブルについて知る必要があります。
なぜならスタブやモックはテストダブルの種類の一つだからです。
Wikipediaではテストダブルを以下のように説明されています。
テストダブル (Test Double) とは、ソフトウェアテストにおいて、テスト対象が依存しているコンポーネントを置き換える代用品のこと。ダブルは代役、影武者を意味する。
// 中略
ジェラルド・メサローシュは、テストダブルのパターンとして、次の5つを挙げている。
– テストスタブ (テスト対象に「間接的な入力」を提供するために使う。)
-モックオブジェクト (テスト対象からの「間接的な出力」を検証するために使う。テストコードの実行前に、あらかじめ期待する結果を設定しておく。検証はオブジェクト内部で行われる。)
// ※他3つは省略
Wikipedia
つまり、テストダブルはオブジェクトやメソッドを代役となるオブジェクトやメソッドに置き換えてテストを行うためのものです。
スタブとは
PHPUnitの公式ドキュメントには以下のような説明があります。
スタブ
実際のオブジェクトを置き換えて、 設定した何らかの値を (オプションで) 返すようなテストダブルのことを スタブ といいます。 スタブ を使うと、 「SUT が依存している実際のコンポーネントを置き換え、 SUT の入力を間接的にコントロールできるようにすることができます。 これにより、SUT が他の何者も実行しないことを強制させることができます。」
PHPUnit 8.テストダブル
つまりスタブとは、オブジェクトやメソッドを決められた値を返すように置き換えたものです。
スタブを使用する目的は、想定する値を返すためです。
モックとは
PHPUnitの公式ドキュメントには以下のような説明があります。
実際のオブジェクトを置き換えて、 (メソッドがコールされたことなどの) 期待する内容を検証するテストダブルのことを モック といいます。
モックオブジェクト は “SUT の間接的な出力の内容を検証するために使用する観測地点です。 一般的に、モックオブジェクトにはテスト用スタブの機能も含まれます。 まだテストに失敗していない場合に、間接的な出力の検証用の値を SUT に返す機能です。 したがって、モックオブジェクトとは テスト用スタブにアサーション機能を足しただけのものとは異なります。 それ以外の用途にも使うことができます” (Gerard Meszaros)。
PHPUnit 8.テストダブル
少し分かりづらいですね。
モックとは、スタブの機能に加えて、オブジェクトやメソッドを実際に実行せずに、呼び出し回数・引数意図通りか確認するものです。
スタブのサンプルコード
では実際にスタブのサンプルコードを見てみましょう。
class Sample
{
/**
* 1〜10の数字をランダムで返す
* @return int
*/
public function getRandomNumberFromOneToTen(): int
{
return rand(1, 10);
}
}
class Something
{
/**
* 数字と文字列を結合して返す
* @return stgring
*/
public function combineStringAndNumber(Sample $sample_obj): string
{
$number = $sample_obj->getRandomNumberFromOneToTen();
$string = 'Test_text';
return $string . '_' . $number;
}
}
Sample
クラスのgetRandomNumberFromOneToTen
は1〜10のランダムな数値を返すので、テストを実行する度に返り値が変わってしまいます。
テストを実行する度に返り値が変わるようでは、テストをすることが難しいですね。
「テストのために決められた値を返したい」そんなときに利用するのがスタブです。
スタブを使ってSomething
クラスのテストコードを使うと以下のようになります。
class SomethingTest extends TestCase
{
/**
* combineStringAndNumberのテスト
* @return void
*/
public function testCombineStringAndNumber(): void
{
// Sampleクラスのスタブを作成
$stub = $this->createStub(Sample::class);
// 5を返すようにスタブを設定
$stub->method('getRandomNumberFromOneToTen')
->willReturn(5);
$string = 'Test_text';
// $stub->getRandomNumberFromOneToTen() は 5 を返す
$number = $stub->getRandomNumberFromOneToTen();
$this->assertSame('Test_text_5', $string . '_' . $number);
}
}
上記のようにすることで1〜10のランダムな返り値を5に固定させテストすることができます。
このようにテストを実行する度に返り値が異なるメソッドの返却値を、決めた値に固定する場合にスタブを使います。
モックのサンプルコード
では次にモックのサンプルコードを見てみましょう。
interface SampleServiceInterface
{
/**
* 1〜10の数字をランダムで返す
* @return int
*/
public function getRandomNumberFromOneToTen(): int;
}
class SampleService implements SampleServiceInterface
{
/**
* @inheritDoc
*/
public function getRandomNumberFromOneToTen(): int
{
return rand(1, 10);
}
}
class Something
{
/**
* 数字と文字列を結合して返す
* @return stgring
*/
public function combineStringAndNumber(Sample $sample_obj): string
{
$number = $sample_obj->getRandomNumberFromOneToTen();
$string = 'Test_text';
return $string . '_' . $number;
}
}
スタブの利用例の時のサンプルコードとは少し違い、Sample
クラスがサービスコンテナになっています。
この場合、モックを使ってSomething
クラスのテストコードを使うと以下のようになります。
use Mockery;
use Mockery\MockInterface;
use App\Services\SampleService;
class SomethingTest extends TestCase
{
/**
* combineStringAndNumberのテスト
* @return void
*/
public function testCombineStringAndNumber(): void
{
// SampleServiceクラスのモックを作成
$mock = Mockery::mock(
SampleService::class,
function (MockInterface $mock) {
$mock->shouldReceive('getRandomNumberFromOneToTen')
->once() // getRandomNumberFromOneToTenメソッドが一回呼ばれていることをテストする
->andReturn(5); // getRandomNumberFromOneToTenメソッドが5を返すようにする
}
);
// SampleServiceの実装クラスと上で作成したモックを結合する
$this->app->instance(SampleService::class, $mock);
$string = 'Test_text';
// $mock->getRandomNumberFromOneToTen() は 5 を返す
$number = $mock->getRandomNumberFromOneToTen();
$this->assertSame('Test_text_5', $string . '_' . $number);
}
}
once()
でメソッドが一回呼ばれていることをテストします。
二回呼ばれていることをテストするならtwice()
、
三回以上ならtimes($n)
でテストします。
このようにテストを実行する度に返り値が異なるメソッドの返却値を、決めた値に固定する場合、メソッドの呼び出し回数をテストしたい場合にモックを使います。
(サンプルコードには記述していませんが、メソッドの引数をテストしたい場合にもモックを使います。)
モックは他にも様々な機能があるので、興味ある方は以下のの公式ドキュメントを読んでみることをおすすめします。
Mockery1.0