FuelPHP でユニットテストを行い、カバレッジも取得してみた。
導入
今回は AWS EC2 上に環境を構築した。
- PHP 7
- MySQL 5.7
- Nginx
- FuelPHP 1.8
- PHPUnit 5.5
- AspectMock 1.0
PHP7 と Nginx と MySQL5.7 のインストール
# nginx
$ sudo yum install -y nginx
# php7
$ sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
$ sudo yum --disablerepo=* --enablerepo=remi install -y php70 php70-php-fpm php70-php-devel php70-php-mbstring php70-php-pdo php70-php-mysql php70-php-mysqlnd php70-php-pecl-xdebug
# mysql
$ sudo rpm -ihv http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpm
$ sudo yum install -y mysql-server
PHP7 をインストールするときに、 --disablerepo=* を指定しなかったら、
amzn リポジトリと remi リポジトリが混じって入ってしまった。
このせいで、やり直す羽目に。
※ xdebug はカバレッジを取得するために必要。
PHPバージョン確認
# Path を通す
# ~/.bash_profile の末尾にも追加しておく
$ source /opt/remi/php70/enable
# バージョン確認
$ php -v
PHP 7.0.11 (cli) (built: Sep 14 2016 07:27:30) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
with Xdebug v2.4.1, Copyright (c) 2002-2016, by Derick Rethans
xdebug もインストールされていることを確認できた。
サービス起動
Nginx と MySQL と FPM (FastCGI Process Manager) を起動する。
# FPM
$ sudo service php70-php-fpm start
Starting php-fpm: [ OK ]
# Nginx
$ sudo service nginx start
Starting nginx: [ OK ]
# MySQL
$ sudo service mysqld start
Initializing MySQL database: [ OK ]
Installing validate password plugin: [ OK ]
Starting mysqld: [ OK ]
サーバが起動した際に自動的にサービスが開始するようにしておく。
$ sudo chkconfig nginx on
$ sudo chkconfig php70-php-fpm on
$ sudo chkconfig mysqld on
MySQLバージョン確認
$ mysql --version
mysql Ver 14.14 Distrib 5.7.15, for Linux (x86_64) using EditLine wrapper
FuelPHP のインストール
ドキュメントに従ってクイックインストール!
# oil を Web からクイックインストールします
$ curl get.fuelphp.com/oil | sh
プロジェクトを作成
作成したいディレクトリへ移動し、以下のコマンドを実行。
# sample プロジェクト作成
$ oil create sample
# FuelPHP がアクセスできるように書き込み権限を与える
$ oil refine install
Made writable: APPPATH/cache
Made writable: APPPATH/logs
Made writable: APPPATH/tmp
Made writable: APPPATH/config
Nginx の設定
FuelPHP のサイト を参考に /etc/ngix/nginx.conf を編集する。
FuelPHP の確認
Document Root にアクセスして Welcome ページが表示されれば、無事インストール完了!
PHPUnit と AspectMock のインストール
AspectMock は普通の mocking framework ではないってAspectMock のサイトに書いてあるし、
「普通じゃないモッキングフレームワークAspectMockがパワフル過ぎる」でもパワフル過ぎるって書いてあるので使ってみる。
まずは、プロジェクト・ディレクトリに移動して、composer.json に追加。
"require-dev": {
"phpunit/phpunit": "5.5.*",
"codeception/aspect-mock": "1.*"
},
codeception/aspect-mock のバージョンを "*"
にすると 2.*
がインストールされるが、
実行時に「Uncaught exception 'InvalidArgumentException' 」が発生するので、
バージョンを 1.*
に落としてインストール。
$ php composer.phar update
このままでは PHPUnit への Path が通っていないので、oil.php で Path を設定する。
# oil.php を app/config へコピーする
cp fuel/packages/oil/config/oil.php fuel/app/config/.
// Path を設定
'autoload_path' => '' ,
'binary_path' => DOCROOT.'fuel/vendor/bin/phpunit' ,
AspectMock を入れると oil test で例外(Exception: Serialization of 'Closure' is not allowed)
が発生するので、phpunit.xml を修正する。
# phpunit.xml をコピーする
$ cp fuel/core/phpunit.xml fuel/app/.
<!--
<phpunit colors="true" stopOnFailure="false" bootstrap="../core/bootstrap_phpunit.php">
-->
<phpunit colors="true" stopOnFailure="false" bootstrap="../core/bootstrap_phpunit.php" backupGlobals="false">
早速 Core モジュールをテストしてみる。
$ oil test --group=Core
Tests Running...This may take a few moments.
PHPUnit 5.5.5 by Sebastian Bergmann and contributors.
............................................................... 63 / 402 ( 15%)
............................................................... 126 / 402 ( 31%)
............................................................... 189 / 402 ( 47%)
F.............................................................. 252 / 402 ( 62%)
............................................................... 315 / 402 ( 78%)
............................................................... 378 / 402 ( 94%)
........................ 402 / 402 (100%)
Time: 744 ms, Memory: 14.00MB
There was 1 failure:
1) Fuel\Core\Test_Format::test_to_xml_boolean with data set #0 (array(array('Value 1', 35, true, false)), '<?xml version="1.0" encoding=.../xml>\n', '<?xml version="1.0" encoding=.../xml>\n', '<?xml version="1.0" encoding=.../xml>\n', '<?xml version="1.0" encoding=.../xml>\n')
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
'<?xml version="1.0" encoding="utf-8"?>
-<xml><item><field1>Value 1</field1><field2>35</field2><field3>1</field3><field4></field4></item></xml>
+<xml><item><field1>Value 1</field1><field2>35</field2><field3>1</field3><field4/></item></xml>
'
/home/ec2-user/tmp/sample/fuel/core/tests/format.php:425
FAILURES!
Tests: 402, Assertions: 495, Failures: 1.
なんと、私の環境ではテストが1つ失敗してしまった!!!
どうも libxml のバージョン絡みらしいが、ちょっと調べた程度では解決できなかった。
今のところ支障がないので、このまま放置。 ♪~( ̄ε ̄;)
ユニットテスト
やっと環境が整ったので、テストケースを作成していく。
テスト対象の作成
まずは DB の接続設定を行う。(DB sample は作成しておくこと)
return array(
'default' => array(
'connection' => array(
'dsn' => 'mysql:host=localhost;dbname=sample',
'username' => '********',
'password' => '********',
),
),
);
次にテスト対象をスキャフォールドで作成する。
Generate - Oil パッケージ - FuelPHP ドキュメント #スキャフォールド
# scaffold
$ oil g scaffold monkey name:string description:text
Creating migration: APPPATH/migrations/001_create_monkeys.php
Creating model: APPPATH/classes/model/monkey.php
Creating controller: APPPATH/classes/controller/monkey.php
Creating view: APPPATH/views/monkey/index.php
Creating view: APPPATH/views/monkey/view.php
Creating view: APPPATH/views/monkey/create.php
Creating view: APPPATH/views/monkey/edit.php
Creating view: APPPATH/views/monkey/_form.php
Creating view: APPPATH/views/template.php
# migrate
$ oil refine migrate
Performed migrations for app:default:
001_create_monkeys
テーブルが作成されたか確認する。
mysql> use sample
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+------------------+
| Tables_in_sample |
+------------------+
| migration |
| monkeys |
+------------------+
2 rows in set (0.00 sec)
mysql>
このままでは、 Orm パッケージが読み込まれないので config.php の以下のコメントを外す。
// 'always_load' => array(
(中略)
// 'packages' => array(
// //'orm',
// ),
(中略)
// ),
何故 monkey なのかわからないが。。。とりあえず上手くいった。
この後のテストのためにデータを1件登録しておく。
カバレッジ取得設定
カバレッジを取得するために phpunit.xml に設定を追加。
PHPUnit マニュアル - 付録C XML 設定ファイル
<logging>
<log type="coverage-html" target="../app/logs/test/coverage"
lowUpperBound="35" highLowerBound="70"/>
</logging>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../app</directory>
<exclude>
<directory suffix=".php">../app/tests</directory>
<directory suffix=".php">../app/vendor</directory>
<file>../app/bootstrap.php</file>
</exclude>
</whitelist>
</filter>
Model のテスト
最初に Model のテストケースを作成してみる。
validate メソッドしかないけどね。
<?php
/**
* Model Monkey class tests
*
* @group App
* @group Model
*/
class Test_Model_Monkey extends TestCase
{
public function test_validate_成功()
{
$val = Model_Monkey::validate('Test_Model_Monkey::test_validate_成功');
// name も description もあり
$actual = $val->run(
[
'name' => '孫悟空',
'description' => 'ドラゴンボールの主人公'
]);
$this->assertTrue($actual);
}
public function test_validate_失敗()
{
// Validation オブジェクト取得
$val = Model_Monkey::validate('Test_Model_Monkey::test_validate_失敗');
// name も description もなし
$actual = $val->run([]);
$this->assertFalse($actual);
// name なし
$actual = $val->run(['description' => 'ドラゴンボールの主人公']);
$this->assertFalse($actual);
// description なし
$actual = $val->run(['name' => '孫悟空']);
$this->assertFalse($actual);
}
}
テストを実行。
$ oil test --group=App
Tests Running...This may take a few moments.
PHPUnit 5.5.5 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 338 ms, Memory: 14.00MB
OK (2 tests, 4 assertions)
Generating code coverage report in HTML format ... done
さっそくカバレッジを見てみる。
出力されたカバレッジをブラウザから見えるように nginx.conf に location を追加する。
location /coverage/ {
alias /○○○○○○/sample/fuel/app/logs/test/coverage/;
}
validate メソッドが 100% になってることが確認できた。
Controller のテスト
簡単そうな action_view メソッド をテストしてみる。
Controller は Model に依存しているので、Model をモック化(テストダブルに)してテストする。
Model_Monkey::find() は static method ですが AspectMock なら大丈夫!
<?php
use AspectMock\Test as test;
/**
* Controller Monkey class tests
*
* @group App
* @group Controller
*/
class Test_Controller_Monkey extends TestCase
{
protected function tearDown()
{
test::clean(); // remove all registered test doubles
}
public function test_action_view_成功()
{
$expected =
[
'name' => '孫悟空',
'description' => 'ドラゴンボールの主人公'
];
// mock
$model = test::double('Model_Monkey', ['find' => new Model_Monkey($expected)]);
$response = Request::forge('monkey/view/2')
->set_method('GET')
->execute()
->response();
$model->verifyInvokedOnce('find', [2]);
$this->assertEquals(200, $response->status);
$this->assertEquals('Monkey', $response->body->title);
$actual = $response->body->content->monkey;
$this->assertEquals($expected['name'], $actual['name']);
$this->assertEquals($expected['description'], $actual['description']);
}
}
テストを実行してカバレッジを確認。
$ oil test --group=App
Tests Running...This may take a few moments.
PHPUnit 5.5.5 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 389 ms, Memory: 14.00MB
OK (3 tests, 8 assertions)
Generating code coverage report in HTML format ... done
ちゃんとカバレッジが取れている!!!
Dashboard ???
カバレッジページに Dashboard っていうリンクがあったので見てみたら、
循環的複雑度(Cyclomatic complexity)なんかが表示されていた。
この中に CRAP っていうものが表示されているけど、何かわからなかったので調べてみたら、
PHPUnit のマニュアルに載っていた。
コードカバレッジの指標
Change Risk Anti-Patterns (CRAP) Index
Change Risk Anti-Patterns (CRAP) インデックス とは、循環的複雑度と、
あるコード単位のコードカバレッジに基づいて算出される指標です。 複雑度が低く、
適切なテストカバレッジが達成されているコードは、CRAPインデックスの値が
低くなります。
CRAPインデックスを下げるには、テストを書くか、 あるいはリファクタリングで
コードの複雑性を下げます。
だそうです。
へぇ~
PHPUnit もなかなかやりますね!
まとめ
Unit Test をするとき、使える Mocking Framework がないとやる気がでない。(気が萎える)
PHP でも AspectMock というパワフルなものがあったので本当に良かった。