pixiv insideは移転しました! ≫ http://inside.pixiv.blog/

モックオブジェクトを利用したテスト

犬が大好きtantanです。 普段どんな仕事をしているかと聞かれれば、色々やっているとしか答えようがない日々を過ごしています。はい。

さて、本日はモックオブジェクトを利用したテスト方法を簡単に紹介したいと思います。

ウェブサイトの開発をしていると様々な場面で外部と連携するための処理を実装する必要が出てくることがあります。googleとのアカウント連携やクレジットカードを利用した決済処理のようにサードパーティと通信を行う処理を実装する必要が出てくるかもしれません。こういった外部との連携機能をテストするときに役立つのがモックオブジェクトです。

モックオブジェクトとは、テストを行う上で必要な振る舞いを返すために用意されるダミーオブジェクトのことです。このモックオブジェクトにテスト中の振る舞いを設定することで、テスト環境では利用できない機能との連携をテストします。

例えばカード決済を行うクラスのテストをする場合、テスト用のカード情報や環境が与えられている場合はともかくとして、そうでない場合はそのままではテストを実施できません。実際に使えるカードの情報でテストすするわけにもいきませんからね。そのためテスト時は決済用の処理をモックオブジェクトに置き換える必要があります。

モックオブジェクトを利用するうえで念頭に置かないといけないのは、モックオブジェクト化したいインスタンスはテスト時に設定できる必要がある、ということです。

例えば、次のような実装をしてしまうとモックオブジェクトを利用するテストができません。

class Checkout
{
    public function pay()
    {
        $payment_module = new CardPayment();

        // 処理を行う

        try {
            $payment_module->execute();   // 外部サーバとの決済処理
        } catch(Exception $e) {
            // 例外処理を行う
        }
        // 処理を行う
    }
}

CardPymentクラスはサードパーティ製のクラスで、決済サーバと通信して決済処理を実行します。テスト中は決済サーバと通信させたくないためCardPaymentクラスはモックオブジェクトに置き換える必要があります。しかしこの場合、payメソッドの中でインスタンス化されているためテスト中にこれを変更することができません。

モックオブジェクトを利用したテストを実施するためには処理の実行時に外部からインスタンスを設定できるようにする必要があります。そこでCheckoutクラスにセッターを追加してテスト中にモックオブジェクトを設定できるようにします。

class Checkout
{
    private $payment_module;


    public function pay()
    {
        // 処理を行う

        try {
            $this->payment_module->execute();   // 外部サーバとの決済処理
        } catch(Exception $e) {
            // 例外処理を行う
        }

        // 処理を行う
    }

    public function setPaymentModule($payment_module)
    {
        $this->payment_module = $payment_module;
    }
}

このようなクラスがあったときのテストをphpunitを例として実装すると次のようになります。

class CheckoutTest extends PHPUnit_Extensions_Database_TestCase
{
    public function test_pay()
    {
        $checkout = new Checkout();

        // CardPaymentのexecuteメソッドの振る舞いを置き換える
        $mock_card_payment = $this->getMock('CardPayment', array('execute'));
        $mock_card_payment->method('execute')->willReturn(true);

        // 処理を行う

        $checkout->setPaymentModuleForCard($mock_card_payment);
        $checkout->pay();

        // 処理を行う
    }
}

CheckoutTestクラスはCheckoutクラスをテストするクラスです。payメソッドのテスト時にCardPaymentクラスのモックオブジェクトを設定しています。例ではexecuteメソッドがtrueを返すように変更しています。こうすることでexecuteメソッドを無効化してpayメソッドのテストを実行することができます。

例外処理をテストしたければモックオブジェクトに例外をthrowさせるようにします。

class CheckoutTest extends PHPUnit_Extensions_Database_TestCase
{
    public function test_pay()
    {
        $checkout = new Checkout();

        // CardPaymentのexecuteメソッドの振る舞いを置き換える
        $mock_card_payment = $this->getMock('CardPayment', array('execute'));
        $mock_card_payment->method('execute')->will($this->throwException(new Exception('例外が発生しました')));

        // 処理を行う

        $checkout->setPaymentModuleForCard($mock_card_payment);
        $checkout->pay();

        // 処理を行う
    }
}

こうすることで、payメソッド内でexecuteメソッドがExceptionをthrowした場合の振る舞いをテストすることができます。

最初にも触れた通り、モックオブジェクトを利用する場合は予めテストのことを考えて設計しておく必要があります。当然の話ですが、テストするメソッド内ではモックオブジェクトが実行される必要があるため、これを外から設定する仕組みが必要になるからです。

今回の記事ではモックオブジェクトを利用したテスト方法をざっくりと説明しましたがいかがだったでしょうか。ソフトウェアテストが良く分からない、という人の参考に少しでもなれば幸いです。 最近担当する案件がどれもまともなテストがなくてフラストレーションが溜まっていたので憂さ晴らしもかねての記事でした。

明日はRooandQooが何か書いてくれるそうです。お楽しみに。