イマドキのCodeIgniterでPHPUnit入門（Composer不使用編）

今回はPHPでのデファクトな「テスティングフレームワークPHPUnit」×「最新のCodeIgniter」な記事を書こうと思います。

Composerは使いません。Composerを使いたい方は「[イマドキのCodeIgniterでPHPUnit入門](http://qiita.com/kenjis/items/afb9757ac21b6324d25b)」をごらんください。

想定の環境としては、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`をエディタで開き、下の方にあるコメント記号を削除し、コメントアウトされているコードを有効にします。

~~~diff
--- 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:application/tests/libraries/Calculate_test.php
<?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`」で始めるか、

```php
    /**
     * @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:application/libraries/Greeting.php
<?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:application/tests/libraries/Greeting_test.php
<?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()`関数をテストダブルで置き換える方法も使えます。興味がある方は参考文献をごらんください。

どうもありがとうございました。

# 参考文献

* [「いまどき!?」CakePHPでPHPUnit入門 - Qiita](http://qiita.com/t_kamata/items/59bda5df9d9949bcef25)
* [CodeIgniter 3.0をコマンド一発でインストールするCodeIgniter Composer Installer](http://blog.a-way-out.net/blog/2015/04/24/codeigniter-composer-installer/)
* [CodeIgniter 3.0でPHPUnitを使う](http://blog.a-way-out.net/blog/2015/05/19/ci-phpunit-test/)
* [ci-phpunit-testを使って和田卓人さんの『現在時刻に依存するテスト』を書いてみる](http://blog.a-way-out.net/blog/2015/08/13/test-depends-on-time-with-ci-phpunit-test/)
* [PHPUnitマニュアル 4.8](https://phpunit.de/manual/4.8/ja/index.html)
* [CI PHPUnit Test](http://kenjis.github.io/ci-phpunit-test/)