ci-phpunit-testを使って和田卓人さんの『現在時刻に依存するテスト』を書いてみる
2013年に以下のテストに関する記事がありました。
後藤さんが解答されていたやつです。
- 和田卓人さん出題のテスト駆動開発問題『現在時刻とロケールに依存するテスト』をPHPを使ってオブジェクト指向で解答してみました #php #object_oriented|CodeIQ MAGAZINE
今日は、この『現在時刻に依存するテスト』を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ではそれほど一般的でないだけです。
以下の記事をしっかり読むといろいろと参考になると思います。
- これであなたもテスト駆動開発マスター!?和田卓人さんがテスト駆動開発問題を解答コード使いながら解説します~現在時刻が関わるテストから、テスト容易性設計を学ぶ #tdd|CodeIQ MAGAZINE
- 和田卓人さん出題のテスト駆動開発問題『現在時刻とロケールに依存するテスト』をPHPを使ってオブジェクト指向で解答してみました #php #object_oriented|CodeIQ MAGAZINE
関連
Date: 2015/08/13