今回はPHPでのデファクトな「テスティングフレームワークPHPUnit」×「最新のCodeIgniter」な記事を書こうと思います。
Composerは使いません。Composerを使いたい方は「イマドキのCodeIgniterでPHPUnit入門」をごらんください。
想定の環境としては、PHP 5.4以上が入ってる前提です。
1. CodeIgniter 3.0のインストール
https://github.com/bcit-ci/CodeIgniter/releases より最新のCodeIgniterのZipファイルをダウンロードします。
現在の最新版はCodeIgniter-3.0.1.zip
です。
ダウンロードしたZipファイルを展開するとCodeIgniter-3.0.1
フォルダが作成されます。
2. PHPUnitのインストール
https://phar.phpunit.de/phpunit.phar をダウンロードします。
① PHPUnitのインストール
ダウンロードしたphpunit.phar
をCodeIgniter-3.0.1
フォルダに移動します。
これで、プロジェクト配下にphpunit.phar
コマンドがインストールされました。
$ cd CodeIgniter-3.0.1/
$ php phpunit.phar --version
PHPUnit 4.8.5 by Sebastian Bergmann and contributors.
② ci-phpunit-testのインストール
CodeIgniterでPHPUnitを簡単に使うためのブリッジツールであるci-phpunit-testをインストールします。
https://github.com/kenjis/ci-phpunit-test/releases より最新のci-phpunit-testのZipファイルをダウンロードします。
現在の最新版はci-phpunit-test-0.6.2.zip
です。
ダウンロードしたZipファイルを展開するとci-phpunit-test-0.6.2
フォルダが作成されます。
ci-phpunit-test-0.6.2
フォルダの中のapplication/tests
フォルダをCodeIgniterプロジェクトのapplication
フォルダにコピーします。
CodeIgniter-3.0.1/
└── application/
└── tests/ <-- ここに配置
③ 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/
$ php ../../phpunit.phar
PHPUnit 4.8.5 by Sebastian Bergmann and contributors.
...
Time: 6.85 seconds, Memory: 17.00Mb
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)
3. 基本的なテストケース
四則計算してくれるライブラリ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()
メソッドもあります。
4. テストダブル
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
になります。
テストを実行してみましょう。
$ php ../../phpunit.phar libraries/Greeting_test.php
PHPUnit 4.8.5 by Sebastian Bergmann and contributors.
..
Time: 1.7 seconds, Memory: 14.50Mb
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()
関数をテストダブルで置き換える方法も使えます。興味がある方は参考文献をごらんください。
どうもありがとうございました。