CodeIgniterとPHPUnitでテストが難しいコードを簡単にテストする(オブジェクトメソッドの置換)

従来、CodeIgniterのアプリはテストが書きにくいと言われてきました。

しかし、最新のCodeIgniter 3.0とPHPUnitおよびブリッジツールであるci-phpunit-testを使うと、ユニットテストが難しいと言われるコードのテストも驚くほど簡単に記述することができます。

【注】ここでは、1クラスを1つのユニットと考えることにします。

CodeIgniterとPHPUnitの準備

イマドキのCodeIgniterでPHPUnit入門 - Qiita」での環境を使います。

GitHubにあるソースを使う場合は、以下のようにします。

$ git clone https://github.com/kenjis/imadoki-codeigniter-phpunit.git
$ cd imadoki-codeigniter-phpunit/
$ composer install

ただし、ci-phpunit-testを最新の開発版にアップデートしておきます。これは、libraryのオートローダを使いたいためです(libraryのオートローダはv0.7.0以降に含まれる予定です)。

$ composer require kenjis/ci-phpunit-test:1.0.x@dev
$ php vendor/kenjis/ci-phpunit-test/update.php

テスト対象のクラス

ユニットテストが難しいケースとしてFooクラスを作成します。

このクラスは「PHP - 依存性注入(DI)の解説とやり方 - Qiita」より借用しています。

application/libraries/Foo.php

<?php

class Foo
{
    public function play()
    {
        $bar = new Bar();

        if ($bar->getSomething() === 1) {
            return true;
        }

        return false;
    }
}

FooクラスはBarクラスに依存しており、Fooの中でBarnewされています。

依存しているBarクラスも用意しておきます。

application/libraries/Bar.php

<?php

class Bar
{
    public function getSomething()
    {

    }
}

テストケースクラスの作成

それでは、Fooクラスのテストを作成してみましょう。

application/tests/libraries/Foo_test.php

<?php

class Foo_test extends TestCase
{
    public function setUp()
    {
        $this->obj = new Foo();
    }

    public function test_play_true()
    {
        MonkeyPatch::patchMethod('Bar', ['getSomething' => 1]);

        $this->assertTrue($this->obj->play());
    }

    public function test_play_false()
    {
        MonkeyPatch::patchMethod('Bar', ['getSomething' => 0]);

        $this->assertFalse($this->obj->play());
    }
}

ここでは、ci-phpunit-testのモンキーパッチ機能を使い、BarクラスのgetSomething()メソッドに介入しています。モンキーパッチとはオリジナルのソースコードを変更することなく、実行時にコードを変更することです。

最初のテストメソッドtest_play_true()では、BarクラスのgetSomething()メソッドの返り値を1に変更しています。

MonkeyPatch::patchMethod('Bar', ['getSomething' => 1]);

Barクラスをテストダブルで置き換えている、あるいはBarクラスのモック化と言うこともできるでしょう。

Bar::getSomething()メソッドが1を返すので、Foo::play()メソッドの返り値はtrueになるはずです。

次のテストメソッドtest_play_false()では、BarクラスのgetSomething()メソッドの返り値を0に変更しています。

MonkeyPatch::patchMethod('Bar', ['getSomething' => 0]);

それでは、テストを実行してみましょう。

$ cd application/tests/
$ ../../vendor/bin/phpunit libraries/Foo_test.php
PHPUnit 4.8.5 by Sebastian Bergmann and contributors.

..

Time: 695 ms, Memory: 6.25Mb

OK (2 tests, 2 assertions)

Generating code coverage report in Clover XML format ... done

Generating code coverage report in HTML format ... done

通りました。

このようにユニットテストに必須なことは、依存性注入(DI)ではなく、依存するオブジェクトや依存するクラスをテストダブルで置き換えることです。

まとめ

  • CodeIgniter 3.0とPHPUnit、ci-phpunit-testを使うとテストが難しいと言われるコードのテストも驚くほど簡単に記述することができます。
  • CodeIgniterのアプリはテストが書きにくいというのは過去の話です。
  • テストを書くためだけに依存性注入(DI)を使う必要はありません。

ただし、テストが書けるということと、それが良いテストであることはイコールではありませんし、テスト対象クラスの設計が良いかどうかも別の話です。

依存性注入(DI)が有効なケースもあるでしょうし、そうでない場合もあるでしょう。逆にDIを使えば自動的に良い設計というわけでもないと思います。

良くないテストはリファクタリングを妨げます。しかし、良くないテストでもテストが全くないよりはいいです。

関連

Date: 2015/08/22

Tags: phpunit, codeigniter, testing