| Prev | Next |
オブジェクトに対するコールが正しく行われたかどうかを調べたいこともあるでしょう。
その方法をここで説明します。ここでは、別のオブジェクトを観察している
あるオブジェクトの特定のメソッド (この例では update())
が正しくコールされたかどうかを調べるものとします。
例 10.1 では、まず
PHPUnit_Framework_TestCase クラスの
getMock() メソッド (表 20.7 を参照ください)
を使用して Observer のモックオブジェクトを作成します。
getMock() メソッドの二番目の (オプションの)
パラメータに配列を指定しているので、Observer
クラスの中の update() メソッドについてのみモック実装が作成されます。
次に、PHPUnit が提供する、いわゆる
Fluent Interface
(流れるようなインターフェイス)
を用いてモックの振る舞いや期待する動作を指定します。簡単に言うと、
いくつもの一時オブジェクト (例えば「update()
がコールされることを期待するオブジェクト」と「パラメータに
○○が指定されることを期待するオブジェクト」) を作成して、
期待値を設定したあとにそれらを連結するといった操作は必要ないということです。
その代わりに、例にあるようにメソッドの呼び出しを連結します。
このほうが、より読みやすく "流れるような" コードとなります。
例 10.1: あるメソッドが、指定したパラメータで一度だけコールされることを確かめるテスト
<?php
require_once 'PHPUnit/Framework.php';
class ObserverTest extends PHPUnit_Framework_TestCase
{
public function testUpdateIsCalledOnce()
{
// Observer クラスのモックオブジェクトを作成します。
// 対象とするのは update() メソッドのみです。
$observer = $this->getMock('Observer', array('update'));
// update() メソッドが一度だけコールされ、その際の
// パラメータは文字列 'something' となる、
// ということを期待しています。
$observer->expects($this->once())
->method('update')
->with($this->equalTo('something'));
// Subject オブジェクトを作成し、Observer オブジェクトの
// モックをアタッチします。
$subject = new Subject;
$subject->attach($observer);
// $subject オブジェクトの doSomething() メソッドをコールします。
// これは、Observer オブジェクトのモックの update() メソッドを、
// 文字列 'something' を引数としてコールすることを期待されています。
$subject->doSomething();
}
}
?>
表 10.1 は、モックメソッドの予定実行回数を表すために使用できる matcher の一覧です。
表10.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 回目に実行された際にマッチするオブジェクトを返します。 |
これらの matcher とともに使用する制約については、 表 20.2 で説明しています。
あるいは、Subject の実装をテストする際に
モックオブジェクトを使用せずに 自己シャント (Self Shunt) パターン
を適用する方法もあります。
これは、テストケース自身をスタブとして使用します。
自己シャントというのは医学用語で、薬を注入する場所を確保するため、
チューブを挿入して動脈からとった血液を静脈にもどすことを意味します。
まず、Observer を実装したテストケースクラスを作成します。
Observer は、Subject
を観察したいオブジェクトが実装しなければならないインターフェイスです。
class ObserverTest extends PHPUnit_Framework_TestCase implements Observer
{
}
次に、Observer のメソッドである update()
を実装します。そして、観察対象のオブジェクトである Subject
の状態が変化した場合にそのメソッドが正しくコールされるかどうかを調べます。
public $wasCalled = FALSE;
public function update(Subject $subject)
{
$this->wasCalled = TRUE;
}
さあ、それではテストを書いてみましょう。まず新しい Subject
オブジェクトを作成し、テストオブジェクトをオブザーバとしてアタッチします。
Subject の状態が変化すると (例えば doSomething()
メソッドがコールされるなど)、Subject
オブジェクトは全オブザーバの update()
メソッドをコールしなければなりません。ここではインスタンス変数
$wasCalled を使用し、update()
の中でその値を設定します。これにより、Subject
オブジェクトが期待通りの動作をしているかどうかを確かめます。
public function testUpdate()
{
$subject = new Subject;
$subject->attach($this);
$subject->doSomething();
$this->assertTrue($this->wasCalled);
}
グローバルなインスタンスではなく、新しい Subject
オブジェクトを作成したことに注意してください。スタブを利用する際には、
このような設計をお勧めします。こうすると、
オブジェクト間の結合度が緩やかになり、再利用性が高まります。
自己シャントパターンになじみのない方には、このテストはわかりにくいかもしれません。 いったいここで何が起こっているんだ? テストケース自身がオブザーバになるだって? でも、お決まりのパターンをいったん身につけてしまえば、 これらのテストは簡単に理解できるようになります。 テストの内容をつかむために必要なことは、 すべてひとつのクラスの中に含まれているのですから。
いろいろな要因で失敗する可能性があるテストよりも、 単一の事項のみをテストするテストのほうが情報を得やすくなります。では、 できるだけ外部からの影響を受けないようなテストを作成するには どうすればいいのでしょうか? 単純なことです。 高価で面倒で頼りなくて遅くて複雑なリソースを、 テスト用に自動で生成されたスタブに置き換えればいいのです。たとえば、 複雑な計算結果を単に定数に置き換えたものを、テスト用に作成すればいいのです。
スタブを使用すると、高価な外部リソースを使用することによる問題を解決できます。
たとえばデータベース接続のようなリソースをテスト間で共有するには
PHPUnit_Extensions_TestSetup デコレータを使用すればいいのですが、
テストの目的を考えると、データベースを使用せずにすむのならばそのほうがずっとよいでしょう。
例 10.2 に、スタブメソッドの作成と返り値の設定の方法を示します。
例 10.2: メソッド呼び出しのスタブの作成
<?php
require_once 'PHPUnit/Framework.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testStub()
{
$stub = $this->getMock('SomeClass');
$stub->expects($this->any())
->method('doSomething')
->will($this->returnValue('foo'));
// $stub->doSomething() をコールすると、'foo' を返します。
}
}
?>
表 10.2 は、スタブメソッドの返り値を設定する際に使用できるメソッドの一覧です。
表10.2 スタブの API
| メソッド | 意味 |
|---|---|
PHPUnit_Framework_MockObject_Stub_Return returnValue(mixed $value) | メソッドが実行された場合の返り値を $value に設定します。 |
PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls onConsecutiveCalls(mixed $value, ...) | メソッドが続けて実行された場合に返す値を設定します。 |
また、スタブを使用することで、よりよい設計を行うことができるようにもなります。
あちこちで使用されているリソースを単一の窓口 (façade : ファサード)
経由でアクセスするようにすることで、
それを簡単にスタブに置き換えられるようになります。例えば、
データベースへのアクセスのコードをそこらじゅうにちりばめるのではなく、
その代わりに IDatabase インターフェイスを実装した単一の
Database オブジェクトを使用するようにします。すると、
IDatabase を実装したスタブを作成することで、
それをテストに使用できるようになるのです。同時に、
テストを行う際にスタブデータベースを使用するか
本物のデータベースを使用するかを選択できるようになります。
つまり開発時にはローカル環境でテストし、
統合テスト時には実際のデータベースでテストするといったことができるようになるのです。
スタブ化しなければならない機能は、たいてい同一オブジェクト内で密結合しています。 この機能ををひとつの結合したインターフェイスにまとめることで、 システムのそれ以外の部分との結合を緩やかにすることができます。
| Prev | Next |
Copyright © 2005-2010 Sebastian Bergmann.