こんにちは。tomita@atuwebです。 みなさんテストしていますか?

この記事ではLaravelプロジェクトへのテスト導入について簡単にまとめます。

環境

  • PHP 7
  • Laravel 5.3
  • PHPUnit 5.7

カバレッジの出力はPHPxdebug拡張に依存しておりますため、必要に応じてインストールしてください。

追加するものは、、、ない

Laravel5.3なら、新規プロジェクトをクローンした状態ですぐテストを書くことができる状態ですから、正直テストを始めるための敷居はとても低いですよ。

composer.jsonを開くと、以下の通り、初期状態でPHPUnitMockeryが指定されていることがわかりますね。

"require-dev": {
    "fzaninotto/faker": "~1.4",
    "mockery/mockery": "0.9.*",
    "phpunit/phpunit": "~5.0",
    :
}

phpunit.xml

テストを書き始める前に、phpunitを整備してテスト対象ファイルの設定やカバレッジの出力を設定しましょう。

プロジェクトルート直下にPHPUnitの設定ファイルphpunit.xmlがあります。

こちらは初期状態です。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="bootstrap/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory suffix="Test.php">./tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
    </php>
</phpunit>

filterの設定

「app以下のphpファイルが全てテスト対象」となっているため、<filter>を修正してテスト対象を絞り込みましょう。

設定例

<filter>
    <whitelist processUncoveredFilesFromWhitelist="true">
        <directory suffix=".php">./app/Http/Controllers</directory>
        <directory suffix=".php">./app/Model</directory>
        <directory suffix=".php">./app/Util</directory>
        <exclude>
            <file>./app/Http/Controllers/Controller.php</file>
        </exclude>
    </whitelist>
</filter>

<directory>は複数設定することが可能です。

また、<exclude>に、テストから除外するファイルを定義します
このように、極力「自前で作ったプログラム」のみテストするようにしましょう。


私はアプリケーションのテストではなくて、ビジネスロジック相当がテストできれば良い と考えているため、そのあたりのクラスをカバーするようにしています。

なお、ModelはEloquentではなくて古い概念のModelといえばわかりやすいでしょうか。
コントローラーに複雑な処理は書かずにModelでゴリゴリ書く感じです。

loggingを設定

以下を付け足してテスト結果を出力するようにしましょう。

<logging>
    <log type="coverage-clover" target="build/logs/clover.xml"/>
    <log type="coverage-html"   target="build/html"/>
</logging>

coverage-htmlは手元でカバレッジをチェックする場合に。
coverage-cloverはJenkinsでコードカバレッジ集計してくれるプラグインのCloverに読ませるために使います。

HTMLでカバレッジを出力する場合は大量のファイルが吐き出されますので.gitignoreにbuildを追加しておきましょう。

最終的なxml

参考までに。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="bootstrap/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory suffix="Test.php">./tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app/Http/Controllers</directory>
            <directory suffix=".php">./app/Model</directory>
            <directory suffix=".php">./app/Util</directory>
            <exclude>
                <file>./app/Http/Controllers/Controller.php</file>
            </exclude>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
    </php>
    <logging>
        <log type="coverage-clover" target="build/logs/clover.xml"/>
        <log type="coverage-html"   target="build/html"/>
    </logging>
</phpunit>

テストを追加

artisanコマンドを打ってテスト用のクラスファイルを追加します。

$ php artisan make:test FooTest

tests以下に生成されたファイルを編集してテストを追加していきましょう。
ちなみに、artisanは[アルチザン]と読みますよー。

PHPUnitにテストと認識してもらうためのテストランナーは、次の2種類の書き方があります。
どちらでも好きな方でOKですが、プロジェクトの中では統一しましょうか。

テストランナー1

メソッド名を[test]で開始します。
Foo::findName(int)をテストする場合はtestFindName()みたいな感じです。

public function testFindName()
{
    // May I ask your name?
    $this->assertTrue(Foo::FindName(17), "Laravel!");
}

テストランナー2

/**
 * @test
 * findAllのテスト
 */
public function findName()
{
    // May I ask your name? ?
    $this->assertTrue(Foo::FindName(17), "Laravel!");
}

クラス内にテストランナーが一つも見当たらない場合、速攻叱られますのでお気をつけください。

テストの実行

コマンドラインからテストを実行してみましょう。

このようにすると、tests以下の全てのテストが実行されます。

$ cd path/to/project
$ ./vendor/bin/phpunit

テスト実装中の場合に全てのテストを回す必要はありません。
次のように実行範囲を限定して任意のテストのみ実行し、時間を節約することもできます。

$ ./vendor/bin/phpunit tests/Bar/
$ ./vendor/bin/phpunit tests/Bar/BazTest

テストが完了するとphpunit.xmlで指定したディレクトリ以下にレポートが出力されます。

おわりに

テストがマストではないといっても、プログラムが期待通りに動作しているかテストを書き、できるだけカバレッジをあげていいきましょう。

JUnitではかなり頑張ってもなかなかカバレッジ100%に達することができなかったのですが、 Laravel + PHPUnitだと簡単に100%が並んで爽快 な気分になります。

参考

Ytake Blog.
PHPUnitの設定は正しくしよう
http://blog.comnect.jp.net/blog/123


Laravel: Up and Running: a Framework for Building Modern Php Apps