Gerard Meszaros は、テストダブルの概念を [Meszaros2007] でこのように述べています。
PHPUnit の createMock($type)
メソッドや getMockBuilder($type)
メソッドを使うと、
指定した元インターフェイス (あるいは元クラス) のテストダブルとして振る舞うオブジェクトを自動的に生成することができます。
このテストダブルオブジェクトは、元のオブジェクトを要するすべての場面で使うことができます。
createMock($type)
メソッドは、指定した型 (インターフェイスやクラス)
のテストダブルオブジェクトをその場で返します。
テストダブルの作成は、デフォルトではベストプラクティスに沿って行われます
(元クラスの __construct()
や __clone()
は実行しません)。また、テストダブルのメソッドに渡された引数はクローンされません。
デフォルトと異なる挙動を求める場合は、
getMockBuilder($type)
メソッドを用いてテストダブルの生成処理をカスタマイズする必要があります。
デフォルトでは、元クラスのすべてのメソッドが置き換えられて、
(元のメソッドは呼び出さずに) 単に null
を返すだけのダミー実装になります。たとえば
will($this->returnValue())
メソッドを使うと、
ダミー実装がコールされたときに値を返すよう設定することができます。
final
, private
および
static
メソッドのスタブやモックは作れないことに注意しましょう。
PHPUnit のテストダブル機能ではこれらを無視し、元のメソッドの振る舞いをそのまま維持します。
実際のオブジェクトを置き換えて、 設定した何らかの値を (オプションで) 返すようなテストダブルのことを スタブ といいます。 スタブ を使うと、 「SUT が依存している実際のコンポーネントを置き換え、 SUT の入力を間接的にコントロールできるようにすることができます。 これにより、SUT が他の何者も実行しないことを強制させることができます。」
例 9.2
に、スタブメソッドの作成と返り値の設定の方法を示します。まず、
PHPUnit\Framework\TestCase
クラスの
createMock()
メソッドを用いて
SomeClass
オブジェクトのスタブを作成します
(例 9.1)。
次に、PHPUnit が提供する、いわゆる
Fluent Interface
(流れるようなインターフェイス)
を用いてスタブの振る舞いを指定します。簡単に言うと、
いくつもの一時オブジェクトを作成して、
それらを連結するといった操作は必要ないということです。
そのかわりに、例にあるようにメソッドの呼び出しを連結します。
このほうが、より読みやすく "流れるような" コードとなります。
例 9.1: スタブを作りたいクラス
<?php use PHPUnit\Framework\TestCase; class SomeClass { public function doSomething() { // なにかをします } } ?>
例 9.2: メソッドに固定値を返させるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->willReturn('foo'); // $stub->doSomething() をコールすると // 'foo' を返すようになります $this->assertEquals('foo', $stub->doSomething()); } } ?>
この例がきちんと動作するのは、元のクラスで "method" という名前のメソッドが宣言されていない場合だけです。
元のクラスで "method" という名前のメソッドが宣言されている場合は、
$stub->expects($this->any())->method('doSomething')->willReturn('foo');
としなければいけません。
舞台裏では、createMock()
メソッドが使われたときに
PHPUnit が自動的に、求める振る舞いを実装した新たな PHP のクラスを生成しています。
例 9.3 に例を示します。
これは、モックビルダーの流れるようなインターフェイスを使って、テストダブルの作成方法を設定するものです。
このテストダブルで使っている設定は、createMock()
がデフォルトで使用するベストプラクティスと同じです。
例 9.3: モックビルダー API を使った、生成されるテストダブルクラスの変更
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testStub() { // SomeClass クラスのスタブを作成します $stub = $this->getMockBuilder($originalClassName) ->disableOriginalConstructor() ->disableOriginalClone() ->disableArgumentCloning() ->disallowMockingUnknownTypes() ->getMock(); // スタブの設定を行います $stub->method('doSomething') ->willReturn('foo'); // $stub->doSomething() をコールすると // 'foo' を返すようになります $this->assertEquals('foo', $stub->doSomething()); } } ?>
ここまでの例では、
willReturn($value)
を使ってシンプルな値を返していました。
この構文は、
will($this->returnValue($value))
と同じ意味です。
この長い構文での検証を使うと、より複雑な動きをするスタブも作れます。
時には、メソッドをコールした際の引数のひとつを
(そのまま) スタブメソッドコールの返り値としたいこともあるでしょう。
例 9.4 は、
returnValue()
のかわりに
returnArgument()
を用いてこれを実現する例です。
例 9.4: メソッドに引数のひとつを返させるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testReturnArgumentStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->will($this->returnArgument(0)); // $stub->doSomething('foo') は 'foo' を返します $this->assertEquals('foo', $stub->doSomething('foo')); // $stub->doSomething('bar') は 'bar' を返します $this->assertEquals('bar', $stub->doSomething('bar')); } } ?>
流れるようなインターフェイスをテストするときには、
スタブメソッドがオブジェクト自身への参照を返すようにできると便利です。
例 9.5 は、
returnSelf()
を使ってこれを実現する例です。
例 9.5: スタブオブジェクトへの参照を返すメソッドのスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testReturnSelf() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->will($this->returnSelf()); // $stub->doSomething() は $stub を返します $this->assertSame($stub, $stub->doSomething()); } } ?>
スタブメソッドをコールした結果として、
定義済みの引数リストにあわせて異なる値を返さなければならないこともあるでしょう。
returnValueMap()
を使えば、
マップを作って引数と関連付け、それを返り値に対応させることができます。
例 9.6 を参照ください。
例 9.6: メソッドにマップからの値を返させるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testReturnValueMapStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // 値を返すための、引数のマップを作製します $map = [ ['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h'] ]; // スタブの設定を行います $stub->method('doSomething') ->will($this->returnValueMap($map)); // $stub->doSomething() は、渡した引数に応じて異なる値を返します $this->assertEquals('d', $stub->doSomething('a', 'b', 'c')); $this->assertEquals('h', $stub->doSomething('e', 'f', 'g')); } } ?>
スタブメソッドをコールした結果として固定値
(returnValue()
を参照ください) や (不変の) 引数
(returnArgument()
を参照ください)
ではなく計算した値を返したい場合は、
returnCallback()
を使用します。
これは、スタブメソッドからコールバック関数やメソッドの結果を返させます。
例 9.7
を参照ください。
例 9.7: メソッドにコールバックからの値を返させるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testReturnCallbackStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->will($this->returnCallback('str_rot13')); // $stub->doSomething($argument) は str_rot13($argument) を返します $this->assertEquals('fbzrguvat', $stub->doSomething('something')); } } ?>
コールバックメソッドを設定するよりももう少しシンプルな方法として、
希望する返り値のリストを指定することもできます。この場合に使うのは
onConsecutiveCalls()
メソッドです。
例 9.8
の例を参照ください。
例 9.8: メソッドに、リストで指定した値をその順で返させるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testOnConsecutiveCallsStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->will($this->onConsecutiveCalls(2, 3, 5, 7)); // $stub->doSomething() は毎回異なる値を返します $this->assertEquals(2, $stub->doSomething()); $this->assertEquals(3, $stub->doSomething()); $this->assertEquals(5, $stub->doSomething()); } } ?>
値を返すのではなく、スタブメソッドで例外を発生させることもできます。
例 9.9
に、throwException()
でこれを行う方法を示します。
例 9.9: メソッドに例外をスローさせるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testThrowExceptionStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->will($this->throwException(new Exception)); // $stub->doSomething() は例外をスローします $stub->doSomething(); } } ?>
また、スタブを使用することで、よりよい設計を行うことができるようにもなります。
あちこちで使用されているリソースを単一の窓口 (façade : ファサード)
経由でアクセスするようにすることで、
それを簡単にスタブに置き換えられるようになります。例えば、
データベースへのアクセスのコードをそこらじゅうにちりばめるのではなく、
その代わりに IDatabase
インターフェイスを実装した単一の
Database
オブジェクトを使用するようにします。すると、
IDatabase
を実装したスタブを作成することで、
それをテストに使用できるようになるのです。同時に、
テストを行う際にスタブデータベースを使用するか
本物のデータベースを使用するかを選択できるようになります。
つまり開発時にはローカル環境でテストし、
統合テスト時には実際のデータベースでテストするといったことができるようになるのです。
スタブ化しなければならない機能は、たいてい同一オブジェクト内で密結合しています。 この機能ををひとつの結合したインターフェイスにまとめることで、 システムのそれ以外の部分との結合を緩やかにすることができます。
実際のオブジェクトを置き換えて、 (メソッドがコールされたことなどの) 期待する内容を検証するテストダブルのことを モック といいます。
モックオブジェクト は "SUT の間接的な出力の内容を検証するために使用する観測地点です。 一般的に、モックオブジェクトにはテスト用スタブの機能も含まれます。 まだテストに失敗していない場合に、間接的な出力の検証用の値を SUT に返す機能です。 したがって、モックオブジェクトとは テスト用スタブにアサーション機能を足しただけのものとは異なります。 それ以外の用途にも使うことができます" (Gerard Meszaros)。
そのテストのスコープ内で生成されたモックオブジェクトだけが、PHPUnit による自動検証の対象となります。
たとえば、データプロバイダなどで生成されたモックオブジェクトや
@depends
アノテーションで注入されたオブジェクトについては、PHPUnit では検証しません。
ひとつ例を示します。ここでは、別のオブジェクトを観察している
あるオブジェクトの特定のメソッド (この例では update()
)
が正しくコールされたかどうかを調べるものとします。
例 9.10
は、テスト対象のシステム (SUT) の一部である
Subject
クラスと Observer
クラスのコードです。
例 9.10: テスト対象のシステム (SUT) の一部である Subject クラスと Observer クラス
<?php use PHPUnit\Framework\TestCase; class Subject { protected $observers = []; protected $name; public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } public function attach(Observer $observer) { $this->observers[] = $observer; } public function doSomething() { // なにかをします // ... // なにかしたということをオブザーバに通知します $this->notify('something'); } public function doSomethingBad() { foreach ($this->observers as $observer) { $observer->reportError(42, 'Something bad happened', $this); } } protected function notify($argument) { foreach ($this->observers as $observer) { $observer->update($argument); } } // その他のメソッド } class Observer { public function update($argument) { // なにかをします } public function reportError($errorCode, $errorMessage, Subject $subject) { // なにかをします } // その他のメソッド } ?>
例 9.11
では、モックオブジェクトを作成して
Subject
オブジェクトと Observer
オブジェクトの対話をテストする方法を説明します。
まず
PHPUnit\Framework\TestCase
クラスの
getMockBuilder()
メソッドを使用して Observer
のモックオブジェクトを作成します。
getMock()
メソッドの二番目の (オプションの)
パラメータに配列を指定しているので、Observer
クラスの中の update()
メソッドについてのみモック実装が作成されます。
あるメソッドがコールされたのかどうか、そしてどんな引数を渡してコールされたのかを検証したいので、
expects()
メソッドと with()
メソッドを用意しました。
これらを使って、このやりとりがどのように行われるのかを指定します。
例 9.11: あるメソッドが、指定した引数で一度だけコールされることを確かめるテスト
<?php use PHPUnit\Framework\TestCase; class SubjectTest extends TestCase { public function testObserversAreUpdated() { // Observer クラスのモックを作成します。 // update() メソッドのみのモックです。 $observer = $this->getMockBuilder(Observer::class) ->setMethods(['update']) ->getMock(); // update() メソッドが一度だけコールされ、その際の // パラメータは文字列 'something' となる、 // ということを期待しています。 $observer->expects($this->once()) ->method('update') ->with($this->equalTo('something')); // Subject オブジェクトを作成し、Observer オブジェクトの // モックをアタッチします。 $subject = new Subject('My subject'); $subject->attach($observer); // $subject オブジェクトの doSomething() メソッドをコールします。 // これは、Observer オブジェクトのモックの update() メソッドを、 // 文字列 'something' を引数としてコールすることを期待されています。 $subject->doSomething(); } } ?>
with()
メソッドには任意の数の引数を渡すことができます。
これは、モック対象のメソッドの引数の数に対応します。
メソッドの引数に対して、単なるマッチだけでなくより高度な制約を指定することもできます。
例 9.12: メソッドが引数つきでコールされることを、さまざまな制約の下でテストする例
<?php use PHPUnit\Framework\TestCase; class SubjectTest extends TestCase { public function testErrorReported() { // Observer クラスのモックを作成します。 // reportError() メソッドをモックします。 $observer = $this->getMockBuilder(Observer::class) ->setMethods(['reportError']) ->getMock(); $observer->expects($this->once()) ->method('reportError') ->with( $this->greaterThan(0), $this->stringContains('Something'), $this->anything() ); $subject = new Subject('My subject'); $subject->attach($observer); // doSomethingBad() メソッドは、 // reportError() メソッドを通じてオブザーバにエラーを報告しなければなりません。 $subject->doSomethingBad(); } } ?>
withConsecutive()
メソッドには、
テスト対象の呼び出しにあわせて、引数の配列を好きなだけ渡せます。
個々の配列は制約のリストです。
with()
と同様に、これがモック対象メソッドのそれぞれの引数に対応します。
例 9.13: あるメソッドが、指定した引数つきで 2 回呼び出されることを確かめるテスト
<?php use PHPUnit\Framework\TestCase; class FooTest extends TestCase { public function testFunctionCalledTwoTimesWithSpecificArguments() { $mock = $this->getMockBuilder(stdClass::class) ->setMethods(['set']) ->getMock(); $mock->expects($this->exactly(2)) ->method('set') ->withConsecutive( [$this->equalTo('foo'), $this->greaterThan(0)], [$this->equalTo('bar'), $this->greaterThan(0)] ); $mock->set('foo', 21); $mock->set('bar', 48); } } ?>
callback()
制約を使えば、より複雑な引数の検証ができます。
この制約は、PHP のコールバックを引数として受け取ります。
このコールバックは、検証したい引数を受け取って、検証を通過した場合に true
、
それ以外の場合に false
を返します。
例 9.14: より複雑な引数の検証
<?php use PHPUnit\Framework\TestCase; class SubjectTest extends TestCase { public function testErrorReported() { // Observer クラスのモックを作成します。 // reportError() メソッドをモックします。 $observer = $this->getMockBuilder(Observer::class) ->setMethods(['reportError']) ->getMock(); $observer->expects($this->once()) ->method('reportError') ->with($this->greaterThan(0), $this->stringContains('Something'), $this->callback(function($subject){ return is_callable([$subject, 'getName']) && $subject->getName() == 'My subject'; })); $subject = new Subject('My subject'); $subject->attach($observer); // doSomethingBad() メソッドは、 // reportError() メソッドを通じてオブザーバにエラーを報告しなければなりません。 $subject->doSomethingBad(); } } ?>
例 9.15: メソッドが一度だけ呼ばれ、同じオブジェクトが渡されたことを確かめるテスト
<?php use PHPUnit\Framework\TestCase; class FooTest extends TestCase { public function testIdenticalObjectPassed() { $expectedObject = new stdClass; $mock = $this->getMockBuilder(stdClass::class) ->setMethods(['foo']) ->getMock(); $mock->expects($this->once()) ->method('foo') ->with($this->identicalTo($expectedObject)); $mock->foo($expectedObject); } } ?>
例 9.16: パラメータのクローンの有効にしたモックオブジェクトの作成
<?php use PHPUnit\Framework\TestCase; class FooTest extends TestCase { public function testIdenticalObjectPassed() { $cloneArguments = true; $mock = $this->getMockBuilder(stdClass::class) ->enableArgumentCloning() ->getMock(); // これでモックがパラメータをクローンするようになり、 // identicalTo 制約は失敗します } } ?>
表 A.1 はメソッドの引数に適用できる制約、そして 表 9.1 は起動回数を指定するために使える matcher です。
表9.1 Matchers
Matcher | 意味 |
---|---|
PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount any() | 評価対象のメソッドがゼロ回以上実行された際にマッチするオブジェクトを返します。 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount never() | 評価対象のメソッドが実行されなかった際にマッチするオブジェクトを返します。 |
PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce atLeastOnce() | 評価対象のメソッドが最低一回以上実行された際にマッチするオブジェクトを返します。 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount once() | 評価対象のメソッドが一度だけ実行された際にマッチするオブジェクトを返します。 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount exactly(int $count) | 評価対象のメソッドが指定した回数だけ実行された際にマッチするオブジェクトを返します。 |
PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex at(int $index) | 評価対象のメソッドが $index 回目に実行された際にマッチするオブジェクトを返します。 |
at()
マッチャーのパラメータ $index
は、
指定したモックオブジェクトでの すべてのメソッドの実行
の、ゼロからはじまるインデックスを参照します。
このマッチャーを使うときには注意しましょう。テストが実装の詳細とあまりにも密結合になり、
脆いテストになってしまう可能性があるからです。
最初に説明したとおり、createMock()
メソッドが用いるデフォルトのテストダブル生成方法がニーズを満たさない場合は、
getMockBuilder($type)
メソッドを使えば生成方法をカスタマイズできます。
モックビルダーが提供するメソッドの一覧は、次のとおりです。
setMethods(array $methods)
をモックビルダーオブジェクト上でコールすると、テストダブルで置き換えるメソッドを指定することができます。その他のメソッドの挙動は変更しません。setMethods(NULL)
とすると、どのメソッドも置き換えません。
setConstructorArgs(array $args)
をコールしてパラメータの配列を渡すと、それを元クラスのコンストラクタに渡すことができます (デフォルトのダミー実装では、コンストラクタは置き換えません)。
setMockClassName($name)
を使うと、生成されるテストダブルクラスのクラス名を指定することができます。
disableOriginalConstructor()
を使うと、元クラスのコンストラクタを無効にすることができます。
disableOriginalClone()
を使うと、元クラスのクローンコンストラクタを無効にすることができます。
disableAutoload()
を使うと、テストダブルクラスを生成するときに __autoload()
を無効にすることができます。
Prophecy は 「クセは強いけれども、強力で柔軟な、PHP のオブジェクトモッキングフレームワークです。 最初は phpspec2 のニーズを満たすために作られましたが、今やそれ以外のテスティングフレームワークでも、 最小限の努力で使えるようになりました」 とのことです。
PHPUnit は、Prophecy を使ったテストダブルの作成に標準で対応しています。 例 9.17 は、例 9.11 と同じテストを、Prophecy の理念に沿って表すとどうなるかを示す例です。
例 9.17: あるメソッドが、指定した引数で一度だけコールされることを確かめるテスト
<?php use PHPUnit\Framework\TestCase; class SubjectTest extends TestCase { public function testObserversAreUpdated() { $subject = new Subject('My subject'); // Observer クラスの prophecy を作成します。 $observer = $this->prophesize(Observer::class); // update() メソッドが一度だけコールされ、その際の // パラメータは文字列 'something' となる、 // ということを期待しています。 $observer->update('something')->shouldBeCalled(); // prophecy を公開し、モックオブジェクトを // Subject にアタッチします。 $subject->attach($observer->reveal()); // $subject オブジェクトの doSomething() メソッドをコールします。 // これは、Observer オブジェクトのモックの update() メソッドを、 // 文字列 'something' を引数としてコールすることを期待されています。 $subject->doSomething(); } } ?>
Prophecy を使ってスタブやスパイそしてモックを作ったり設定したり使ったりする方法の詳細については、 その ドキュメント を参照ください。
getMockForTrait()
メソッドは、指定したトレイトを使ったモックオブジェクトを返します。
そのトレイトのすべての抽象メソッドがモックの対象となります。
これを使えば、トレイトの具象メソッドをテストすることができます。
例 9.18: トレイトの具象メソッドのテスト
<?php use PHPUnit\Framework\TestCase; trait AbstractTrait { public function concreteMethod() { return $this->abstractMethod(); } public abstract function abstractMethod(); } class TraitClassTest extends TestCase { public function testConcreteMethod() { $mock = $this->getMockForTrait(AbstractTrait::class); $mock->expects($this->any()) ->method('abstractMethod') ->will($this->returnValue(true)); $this->assertTrue($mock->concreteMethod()); } } ?>
getMockForAbstractClass()
メソッドは、
抽象クラスのモックオブジェクトを返します。
そのクラスのすべての抽象メソッドがモックの対象となります。
これを使えば、抽象クラスにある具象メソッドをテストすることができます。
例 9.19: 抽象クラスの具象メソッドのテスト
<?php use PHPUnit\Framework\TestCase; abstract class AbstractClass { public function concreteMethod() { return $this->abstractMethod(); } public abstract function abstractMethod(); } class AbstractClassTest extends TestCase { public function testConcreteMethod() { $stub = $this->getMockForAbstractClass(AbstractClass::class); $stub->expects($this->any()) ->method('abstractMethod') ->will($this->returnValue(true)); $this->assertTrue($stub->concreteMethod()); } } ?>
ウェブサービスとのやりとりを行うアプリケーションを、
実際にウェブサービスとやりとりすることなくテストしたくなることもあるでしょう。
ウェブサービスのスタブやモックを作りやすくするために getMockFromWsdl()
メソッドが用意されており、これは getMock()
(上を参照ください)
とほぼ同様に使うことができます。唯一の違いは、
getMockFromWsdl()
が返すスタブやモックが WSDL
のウェブサービス記述にもとづくものであるのに対して getMock()
が返すスタブやモックが PHP のクラスやインターフェイスにもとづくものであるという点です。
例 9.20
は、getMockFromWsdl()
を使って
GoogleSearch.wsdl
に記述されたウェブサービスのスタブを作る例です。
例 9.20: ウェブサービスのスタブ
<?php use PHPUnit\Framework\TestCase; class GoogleTest extends TestCase { public function testSearch() { $googleSearch = $this->getMockFromWsdl( 'GoogleSearch.wsdl', 'GoogleSearch' ); $directoryCategory = new stdClass; $directoryCategory->fullViewableName = ''; $directoryCategory->specialEncoding = ''; $element = new stdClass; $element->summary = ''; $element->URL = 'https://phpunit.de/'; $element->snippet = '...'; $element->title = '<b>PHPUnit</b>'; $element->cachedSize = '11k'; $element->relatedInformationPresent = true; $element->hostName = 'phpunit.de'; $element->directoryCategory = $directoryCategory; $element->directoryTitle = ''; $result = new stdClass; $result->documentFiltering = false; $result->searchComments = ''; $result->estimatedTotalResultsCount = 3.9000; $result->estimateIsExact = false; $result->resultElements = [$element]; $result->searchQuery = 'PHPUnit'; $result->startIndex = 1; $result->endIndex = 1; $result->searchTips = ''; $result->directoryCategories = []; $result->searchTime = 0.248822; $googleSearch->expects($this->any()) ->method('doGoogleSearch') ->will($this->returnValue($result)); /** * $googleSearch->doGoogleSearch() はスタブが用意した結果を返し、 * ウェブサービスの doGoogleSearch() が呼び出されることはありません */ $this->assertEquals( $result, $googleSearch->doGoogleSearch( '00000000000000000000000000000000', 'PHPUnit', 0, 1, false, '', false, '', '', '' ) ); } } ?>
vfsStream は 仮想ファイルシステム 用の ストリームラッパー で、 ユニットテストにおいて実際のファイルシステムのモックを作るときに有用です。
Composer
を使ってプロジェクトの依存関係を管理するには、
mikey179/vfsStream
への依存情報をプロジェクトの
composer.json
ファイルに追加します。
次に示すのは最小限の
composer.json
ファイルの例で、
開発時の PHPUnit 4.6 と vfsStream への依存を定義しています。
{ "require-dev": { "phpunit/phpunit": "~4.6", "mikey179/vfsStream": "~1" } }
例 9.21 は、ファイルシステムを操作するクラスの例です。
例 9.21: ファイルシステムを操作するクラス
<?php use PHPUnit\Framework\TestCase; class Example { protected $id; protected $directory; public function __construct($id) { $this->id = $id; } public function setDirectory($directory) { $this->directory = $directory . DIRECTORY_SEPARATOR . $this->id; if (!file_exists($this->directory)) { mkdir($this->directory, 0700, true); } } }?>
vfsStream のような仮想ファイルシステムがなければ、外部への影響なしに
setDirectory()
メソッドを個別にテストすることができません
(例 9.22
を参照ください)。
例 9.22: ファイルシステムを操作するクラスのテスト
<?php use PHPUnit\Framework\TestCase; class ExampleTest extends TestCase { protected function setUp() { if (file_exists(dirname(__FILE__) . '/id')) { rmdir(dirname(__FILE__) . '/id'); } } public function testDirectoryIsCreated() { $example = new Example('id'); $this->assertFalse(file_exists(dirname(__FILE__) . '/id')); $example->setDirectory(dirname(__FILE__)); $this->assertTrue(file_exists(dirname(__FILE__) . '/id')); } protected function tearDown() { if (file_exists(dirname(__FILE__) . '/id')) { rmdir(dirname(__FILE__) . '/id'); } } } ?>
この方式には、次のような問題があります。
外部のリソースを使うため、ファイルシステムのテストが断続的になる可能性があります。その結果、テストがあまり当てにならないものになります。
setUp()
と tearDown()
で、テストの前後にそのディレクトリがないことを確認する必要があります。
tearDown()
メソッドを実行する前にテストが異常終了したときに、ファイルシステム上にディレクトリが残ったままとなります。
例 9.23 は、vfsStream を使ってファイルシステムのモックを作成し、 ファイルシステムを操作するクラスのテストを行う例です。
例 9.23: ファイルシステムを操作するクラスのテストにおけるファイルシステムのモックの作成
<?php use PHPUnit\Framework\TestCase; class ExampleTest extends TestCase { public function setUp() { vfsStreamWrapper::register(); vfsStreamWrapper::setRoot(new vfsStreamDirectory('exampleDir')); } public function testDirectoryIsCreated() { $example = new Example('id'); $this->assertFalse(vfsStreamWrapper::getRoot()->hasChild('id')); $example->setDirectory(vfsStream::url('exampleDir')); $this->assertTrue(vfsStreamWrapper::getRoot()->hasChild('id')); } } ?>
この方式には次のような利点があります。
テストが簡潔になります。
vfsStream が、テスト対象のコードから操作するファイルシステム環境を用意してくれるので、開発者はそれを自由に扱えるようになります。
実際のファイルシステムを操作することがなくなるので、tearDown()
メソッドでの後始末が不要になります。