ci-phpunit-testを使って和田卓人さんの『現在時刻に依存するテスト』を書いてみる

2013年に以下のテストに関する記事がありました。

後藤さんが解答されていたやつです。

今日は、この『現在時刻に依存するテスト』をci-phpunit-testを使って書いてみようと思います。

セットアップ

CodeIgniter 3.0をインストールします。

$ composer create-project kenjis/codeigniter-composer-installer ci-tdd

ci-phpunit-testをインストールします。

$ cd ci-tdd/
$ composer require kenjis/ci-phpunit-test --dev
$ php vendor/kenjis/ci-phpunit-test/install.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',
 ]);
-*/

 /*
  * -------------------------------------------------------------------

これで準備完了です。

問題

【仕様1】 「現在時刻」に応じて、挨拶の内容を下記のようにそれぞれ返す機能を作成したい。 (タイムゾーンはAsia/Tokyoとする)

  • 朝(05:00:00以上 12:00:00未満)の場合、「おはようございます」と返す
  • 昼(12:00:00以上 18:00:00未満)の場合、「こんにちは」と返す
  • 夜(18:00:00以上 05:00:00未満)の場合、「こんばんは」と返す

例: 13時にgreeter.greet()を呼ぶと”こんにちは”と返す

これが問題です。

Greeterモデル

TDDでなくてすみません。Greeterモデルを先に作成します。

application/models/Greeter.php

<?php

class Greeter
{
    public function greet()
    {
        $hour = date('H');

        if ($hour >= 5 && $hour < 12) {
            return 'おはようございます';
        } elseif ($hour >= 12 && $hour < 18) {
            return 'こんにちは';
        } else {
            return 'こんばんは';
        }
    }
}

どうでしょうか?意図は明確でしょうか?

素朴に素直に、ある意味あまり考えずに書くと、このようになるのではないかと思います。

Greeterモデルのテスト

それではGreeterモデルのテストを書いてみましょう。

application/tests/models/Greeter_test.php

<?php

class Greeter_test extends TestCase
{
    public function setUp()
    {
        $this->resetInstance();
        $this->CI->load->model('Greeter');
        $this->obj = $this->CI->Greeter;
    }

    /**
     * @dataProvider provide_hours
     */
    public function test_greet($hour, $msg)
    {
        MonkeyPatch::patchFunction('date', $hour, 'Greeter');

        $actual = $this->obj->greet();
        $this->assertEquals($msg, $actual);
    }

    public function provide_hours()
    {
        return [
            ['00', 'こんばんは'],
            ['04', 'こんばんは'],
            ['05', 'おはようございます'],
            ['11', 'おはようございます'],
            ['12', 'こんにちは'],
            ['17', 'こんにちは'],
            ['18', 'こんばんは'],
            ['23', 'こんばんは'],
        ];
    }
}

ポイント1 TestCaseクラスを継承する

TestCaseクラスを継承すると(PHPUnitおよび)ci-phpunit-testの様々な機能を使えます。

ポイント2 setUp()メソッドでのテストの準備

ここで、モデルクラスのロードおよびインスタンス化をしています。CodeIgniter風のコードです。

もし、これが気に入らない人は、Composerのオートローダを使うようにして、

$this->obj = new App\Model\Greeter();

みたいにしてください。

ポイント3 パラメタライズドテスト

PHPUnitの@dataProviderアノテーションを使いパラメタライズドテストをしています。

指定したメソッドの返り値が、順にテストメソッドの引数に渡され、テストが実行されます。

ポイント4 関数へのモンキーパッチ

ci-phpunit-testのモンキーパッチ機能(関数パッチ)を使い、Greeterクラス内のdate()関数の返り値を動的に変更しています。

MonkeyPatch::patchFunction('date', $hour, 'Greeter');

【注意】この機能はすべての関数を変更できるわけではありません。詳細は、ドキュメント(英語)を参照してください。

テストの実行

それでは、テストを実行してみましょう。

phpunitコマンドはapplication/testsフォルダに移動して実行してください。

$ cd application/tests/
$ phpunit --debug models/Greeter_test.php
PHPUnit 4.7.7 by Sebastian Bergmann and contributors.
Warning:    The Xdebug extension is not loaded
        No code coverage will be generated.


Starting test 'Greeter_test::test_greet with data set #0 ('00', 'こんばんは')'.
.
Starting test 'Greeter_test::test_greet with data set #1 ('04', 'こんばんは')'.
.
Starting test 'Greeter_test::test_greet with data set #2 ('05', 'おはようございます')'.
.
Starting test 'Greeter_test::test_greet with data set #3 ('11', 'おはようございます')'.
.
Starting test 'Greeter_test::test_greet with data set #4 ('12', 'こんにちは')'.
.
Starting test 'Greeter_test::test_greet with data set #5 ('17', 'こんにちは')'.
.
Starting test 'Greeter_test::test_greet with data set #6 ('18', 'こんばんは')'.
.
Starting test 'Greeter_test::test_greet with data set #7 ('23', 'こんばんは')'.
.

Time: 189 ms, Memory: 3.75Mb

OK (8 tests, 8 assertions)

すべて通りました。グリーンです。

まとめ

  • ci-phpunit-testを使うとCodeIgniterアプリのテストを簡単に書くことができます。
  • たぶんテストできないものはありません。あれば、教えて下さい。
  • テストの書き方は1つではありません。いろいろな方法があります。

CodeIgniter 3.0でテストを書きたいという人は、是非、ci-phpunit-testを試してみてください。

なお、このようなコーディングを積極的に推奨しているわけではありません。

とはいえ、

  • 組み込みクラス/メソッド/関数に介入する

というテスト手法は以下の和田さんの記事にも出てくる一般的な方法です。言語機能的な制約からPHPではそれほど一般的でないだけです。

以下の記事をしっかり読むといろいろと参考になると思います。

関連

Date: 2015/08/13

Tags: codeigniter, phpunit, testing