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
の中でBar
がnew
されています。
依存している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を使えば自動的に良い設計というわけでもないと思います。
良くないテストはリファクタリングを妨げます。しかし、良くないテストでもテストが全くないよりはいいです。
- An imperfect test today is better than a perfect test someday.
- An ugly test is better than no test.
-- The Way of Testivus - Unit Testing Wisdom From An Ancient Software Start-up
関連
Date: 2015/08/22