Prev Next

第10章 モックオブジェクト

オブジェクトに対するコールが正しく行われたかどうかを調べたいこともあるでしょう。 その方法をここで説明します。ここでは、別のオブジェクトを観察している あるオブジェクトの特定のメソッド (この例では 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
1. 自動テスト
2. PHPUnit の目標
3. PHPUnit のインストール
4. PHPUnit 用のテストの書き方
5. コマンドラインのテストランナー
6. テストの構成
7. Fixtures
tearDown() よりも setUp()
バリエーション
スイートレベルの設定
8. テストケースの拡張
例外のテスト
出力内容のテスト
パフォーマンス低下のテスト
9. 不完全なテスト・テストの省略
不完全なテスト
テストの省略
10. モックオブジェクト
自己シャント
スタブ
11. テストの進め方
開発中のテスト
デバッグ中のテスト
12. テストファーストプログラミング
銀行口座の例
13. コードカバレッジ解析
14. テストのその他の使用法
アジャイルな文書作成
複数チームでのテスト
15. ログ出力
XML 形式
JavaScript Object Notation (JSON)
Test Anything Protocol (TAP)
GraphViz マークアップ
16. 雛形ジェネレータ
アノテーション
17. PHPUnit と Selenium
Selenium RC
PHPUnit_Extensions_SeleniumTestCase
18. 継続的インテグレーション
CruiseControl
Apache Maven
19. PHPUnit の実装
20. PHPUnit API
概要
PHPUnit_Framework_Assert
PHPUnit_Framework_Test
PHPUnit_Framework_TestCase
PHPUnit_Framework_TestSuite
PHPUnit_Framework_TestResult
パッケージの構成
21. PHPUnit の拡張
PHPUnit_Framework_TestCase のサブクラスの作成
アサートクラスの作成
PHPUnit_Extensions_TestDecorator のサブクラスの作成
PHPUnit_Framework_Test の実装
PHPUnit_Framework_TestResult のサブクラスの作成
PHPUnit_Framework_TestListener の実装
新しいテストランナーの作成
A. PHP 4 用の PHPUnit
B. 目次
C. 参考文献
D. 著作権