LoginSignup
21
26

More than 5 years have passed since last update.

FuelPHP でユニットテスト + カバレッジ取得

Posted at

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 リポジトリが混じって入ってしまった。
このせいで、やり直す羽目に。 :sob:

※ 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 ページが表示されれば、無事インストール完了!

welcome.png

PHPUnit と AspectMock のインストール

AspectMock は普通の mocking framework ではないってAspectMock のサイトに書いてあるし、
普通じゃないモッキングフレームワークAspectMockがパワフル過ぎる」でもパワフル過ぎるって書いてあるので使ってみる。

まずは、プロジェクト・ディレクトリに移動して、composer.json に追加。

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/.
fuel/app/config/oil.php
  // 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/.
fuel/app/phpunit.xml
<!--
<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 は作成しておくこと)

fuel/app/config/development/db.php
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
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 の以下のコメントを外す。

fuel/app/config/config.php
    // 'always_load'  => array(
中略
        // 'packages'  => array(
        //  //'orm',
        // ),
中略
    // ),

ブラウザでアクセスしてみる。
monkeys_01.png

何故 monkey なのかわからないが。。。とりあえず上手くいった。
この後のテストのためにデータを1件登録しておく。
monkeys_02.png

カバレッジ取得設定

カバレッジを取得するために phpunit.xml に設定を追加。
PHPUnit マニュアル - 付録C XML 設定ファイル

fuel/app/phpunit.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 メソッドしかないけどね。

fuel/app/tests/model/monkey.php
<?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 を追加する。

/etc/nginx/nginx.conf
location /coverage/ {
    alias   /○○○○○○/sample/fuel/app/logs/test/coverage/;
}

app
coverage_app.png

app/classes
coverage_app_classes.png

model
coverage_app_classes_model.png

Model_Monkey
coverage_app_classes_model_monkey.png

validate メソッドが 100% になってることが確認できた。

Controller のテスト

簡単そうな action_view メソッド をテストしてみる。

Controller は Model に依存しているので、Model をモック化(テストダブルに)してテストする。
Model_Monkey::find() は static method ですが AspectMock なら大丈夫!

fuel/app/tests/controller/monkey.php
<?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

app/classes
coverage_app_classes02.png

controller
coverage_app_classes_controller.png

Controller_Monkey
coverage_app_classes_controller_monkey.png

ちゃんとカバレッジが取れている!!!

Dashboard ???

カバレッジページに Dashboard っていうリンクがあったので見てみたら、
循環的複雑度(Cyclomatic complexity)なんかが表示されていた。

coverage_app_classes_dashboard.png

この中に CRAP っていうものが表示されているけど、何かわからなかったので調べてみたら、
PHPUnit のマニュアルに載っていた。
コードカバレッジの指標

Change Risk Anti-Patterns (CRAP) Index
 Change Risk Anti-Patterns (CRAP) インデックス とは、循環的複雑度と、
 あるコード単位のコードカバレッジに基づいて算出される指標です。 複雑度が低く、
 適切なテストカバレッジが達成されているコードは、CRAPインデックスの値が
 低くなります。
 CRAPインデックスを下げるには、テストを書くか、 あるいはリファクタリングで
 コードの複雑性を下げます。

だそうです。
へぇ~
PHPUnit もなかなかやりますね!

まとめ

Unit Test をするとき、使える Mocking Framework がないとやる気がでない。(気が萎える)
PHP でも AspectMock というパワフルなものがあったので本当に良かった。

21
26
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
26