今回はPHPでのデファクトな「テスティングフレームワークPHPUnit」×「最新のCodeIgniter」な記事を書こうと思います。
想定の環境としては、PHP 5.4以上が入ってる前提です。
それとComposerを使います。Composerを使いたくない方は「イマドキのCodeIgniterでPHPUnit入門(Composer不使用編)」をごらんください。
1. Composerのインストール
Composerでインストールする方法がイマドキです。なので、まず、Composerをインストールします。
グローバルにインストールする方法とプロジェクト配下にインストールする方法がありますが、ここではグローバルにインストールします。
$ curl -sS https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
これでcomposer
コマンドが使えるようになりました。プロジェクト配下にインストールしたい場合は、以下のComposerのドキュメント(日本語訳)を参照してください。
Windowsの場合はインストーラを使ってインストールする方法が簡単です。以下を参照してください。
2. CodeIgniter 3.0のインストール
composer
コマンドが使えるようになりましたので、まず、CodeIgniter 3.0をComposerでインストールします。
$ composer create-project kenjis/codeigniter-composer-installer codeigniter
これでcodeigniter
フォルダにCodeIgniterがインストールされました。
3. PHPUnitのインストール
PHPUnitもComposerでインストールします。
グローバルにインストールする方法とプロジェクト配下にインストールする方法がありますが、ここではプロジェクト配下にインストールします。
① PHPUnitのインストール
$ cd codeigniter/
$ composer require phpunit/phpunit:4.8.* --dev
ここでは、phpunit/phpunit:4.8.*
とバージョン4.8.xを指定しています。
--dev
オプションはこのパッケージが開発時にのみ必要であることを指定しています。
これで、プロジェクト配下のvendor/bin/phpunit
にphpunit
コマンドがインストールされました。
$ vendor/bin/phpunit --version
PHPUnit 4.8.5 by Sebastian Bergmann and contributors.
② ci-phpunit-testのインストール
CodeIgniterでPHPUnitを簡単に使うためのブリッジツールであるci-phpunit-testをインストールします。
$ composer require kenjis/ci-phpunit-test --dev
$ php vendor/kenjis/ci-phpunit-test/install.php
③ ci-phpunit-testの設定
テストをより簡単に書けるように、ci-phpunit-testのモンキーパッチ機能を有効にしておきます。
application/tests/Bootstrap.php
をエディタで開き、下の方にあるコメント記号を削除し、コメントアウトされているコードを有効にします。
--- a/application/tests/Bootstrap.php
+++ b/application/tests/Bootstrap.php
@@ -295,7 +295,6 @@ switch (ENVIRONMENT)
* If you want to use monkey patching, uncomment below code and configure
* for your application.
*/
-/*
require __DIR__ . '/_ci_phpunit_test/patcher/bootstrap.php';
MonkeyPatchManager::init([
'cache_dir' => APPPATH . 'tests/_ci_phpunit_test/tmp/cache',
@@ -320,7 +319,6 @@ MonkeyPatchManager::init([
],
'exit_exception_classname' => 'CIPHPUnitTestExitException',
]);
-*/
/*
* -------------------------------------------------------------------
これで完了です。
ci-phpunit-testに付属しているサンプルのテストを実行してみましょう。
$ cd application/tests/
$ ../../vendor/bin/phpunit
PHPUnit 4.8.5 by Sebastian Bergmann and contributors.
...
Time: 6.32 seconds, Memory: 10.50Mb
OK (3 tests, 3 assertions)
Generating code coverage report in Clover XML format ... done
Generating code coverage report in HTML format ... done
以下のようにOKと表示された場合は、すべてのテストがパスしたことを意味します。
OK (3 tests, 3 assertions)
4. 基本的なテストケース
四則計算してくれるライブラリCalculate.php
に対して、テストケースを書きたいとします。
テストケースクラスは、application/tests
フォルダ以下に作成します。
<?php
class Calculate_test extends TestCase
{
// セットアップ
public function setUp()
{
$this->resetInstance();
$this->CI->load->library('Calculate');
$this->obj = $this->CI->calculate;
}
// テストケース add(x, y)
public function test_add()
{
$result = $this->obj->add(1, 2);
$this->assertEquals(3, $result);
}
// テストケース multi(x, y)
public function test_multi()
{
$result = $this->obj->multi(4, 6);
$this->assertEquals(24, $result);
}
...
}
テストクラス名(ファイル名)は、「(テスト対象となるクラス名)+_test.php
」となります。
テストクラスのファイル名は、PHPUnitの設定で変更できます。
application/tests/phpunit.xml
が設定ファイルです。
テストクラスはTestCase
クラスを継承します。これはci-phpunit-testのクラスです。
素のPHPUnitの場合は、
PHPUnit_Framework_TestCase
クラスを継承します。
また、テストメソッドは、メソッド名を「test
」で始めるか、
/**
* @test
*/
public function add()
{
$result = $this->obj->add(1, 2);
$this->assertEquals(3, $result);
}
という風に@test
アノテーションをつけると、テストメソッドと見なされます。
setUp()
、tearDown()
メソッドは各テストメソッドの始めと終わりに一度ずつ呼ばれるメソッドです。
setUp()
メソッドでは、テスト対象のオブジェクトをインスタンス化して$this->obj
に代入しています。
素のPHPでオートロード可能な場合、
setUp()
メソッド内のコードは$this->obj = new Calculate();
となります。
$this->resetInstance()
メソッドはci-phpunit-testのメソッドでCodeIgniterインスタンスをリセットし、新しいCodeIgniterインスタンスを$this->CI
にセットします。
tearDown()
メソッドは使っていませんが、使う場合は、忘れずにparent::tearDown();
を記述してください。
その他、各テストクラスに対してそれぞれ一度ずつ呼ばれるsetupBeforeClass()
、tearDownAfterClass()
メソッドもあります。
5. テストダブル
PHPUnitに限らずテストコード開発においては、テストダブル(テストのための代役。モックやスタブとも呼ばれます)を用いることでテスト時のみの特別な挙動を操作したい場合がよくあります。
ここに、実行時間帯によって実行結果が変わってくるsay()
メソッドを含んだGreeting
クラスがあったとします。
<?php
class Greeting
{
private function getCurrentHour()
{
$hour = date('H');
return $hour;
}
public function say()
{
$greeting = '';
$hour = $this->getCurrentHour();
if (6 <= $hour && $hour <= 11) {
$greeting = 'Good Morning.';
} elseif (12 <= $hour && $hour <= 16) {
$greeting = 'Good Afternoon.';
} elseif (17 <= $hour && $hour <= 20) {
$greeting = 'Good Evening.';
} elseif (21 <= $hour && $hour <= 23) {
$greeting = 'Good Night.';
} elseif (0 <= $hour && $hour <= 5) {
$greeting = 'ZZZ...';
}
return $greeting;
}
}
このメソッドに対してそのままテストコードを書いて実行したとすると、テスト実行時間帯が朝か夜かで、アサーション結果が変わってしまいそうです。
時間に依存するメソッドに対するテストは、テストダブルを使うとよいです。
ci-phpunit-testのモンキーパッチ機能を使うと、簡単にユーザ定義クラスのメソッドを置き換えることができ、テストをシンプルに記述できます。
<?php
class Greeting_test extends TestCase
{
public function setUp()
{
$this->resetInstance();
$this->CI->load->library('Greeting');
$this->obj = $this->CI->greeting;
}
// テストケース 朝
public function test_say_good_moring_at_7()
{
MonkeyPatch::patchMethod(
'Greeting',
['getCurrentHour' => '7']
);
$result = $this->obj->say();
$this->assertEquals('Good Morning.', $result);
}
// テストケース 昼
public function test_say_good_afternoon_at_13()
{
MonkeyPatch::patchMethod(
'Greeting',
['getCurrentHour' => '13']
);
$result = $this->obj->say();
$this->assertEquals('Good Afternoon.', $result);
}
...(同じメソッドsay()に対して時間帯条件ごとに複数のテストケース)
}
-
MonkeyPatch::patchMethod()
メソッドの第1引数に置き換えたいクラス名を指定します。 - 第2引数に置き換えたい「メソッド名 => 返り値」を配列で指定します。
-
'Greeting', ['getCurrentHour' => '7']
の場合は、Greeting
クラスのgetCurrentHour()
メソッドの返り値が7
になります。
テストを実行してみましょう。
$ ../../vendor/bin/phpunit libraries/Greeting_test.php
PHPUnit 4.8.5 by Sebastian Bergmann and contributors.
..
Time: 683 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
通りました。
なお、ci-phpunit-testでは、この他にもモンキーパッチ機能でdate()
関数をテストダブルで置き換える方法も使えます。興味がある方は参考文献をごらんください。
どうもありがとうございました。
ソースコード
この記事のソースコードをGitHubに置きました。