イマドキのCodeIgniterでPHPUnit入門

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

想定の環境としては、PHP 5.4以上が入ってる前提です。

それとComposerを使います。Composerを使いたくない方は「[イマドキのCodeIgniterでPHPUnit入門（Composer不使用編）](http://qiita.com/kenjis/items/db3d1db35c9d9af305af)」をごらんください。

# 1. Composerのインストール

Composerでインストールする方法がイマドキです。なので、まず、Composerをインストールします。

グローバルにインストールする方法とプロジェクト配下にインストールする方法がありますが、ここではグローバルにインストールします。

~~~
$ curl -sS https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
~~~

これで`composer`コマンドが使えるようになりました。プロジェクト配下にインストールしたい場合は、以下のComposerのドキュメント（日本語訳）を参照してください。

* [*nixへインストール](https://github.com/kohkimakimoto/getcomposer.org_doc_jp/blob/master/src/doc/00-intro.md#-nix%E3%81%B8%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB-)

Windowsの場合はインストーラを使ってインストールする方法が簡単です。以下を参照してください。

* [Windowsへインストール](https://github.com/kohkimakimoto/getcomposer.org_doc_jp/blob/master/src/doc/00-intro.md#windows%E3%81%B8%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)

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

~~~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/
$ ../../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: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()`メソッドもあります。

# 5. テストダブル

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`になります。

テストを実行してみましょう。

~~~
$ ../../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に置きました。

* https://github.com/kenjis/imadoki-codeigniter-phpunit

# 参考文献

* [「いまどき!?」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/)