テストを行うために必要な知識

Tag:

Laravelでテストを行うために必要な知識について書いてます。

テストに関連するファイル

phpunit.xml

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>
2~10行目
bootstrapでテスト実行前に読み込むファイルを指定します。
stopOnFailureでテストに失敗した場合、残りのテストをキャンセルするか指定します。
11~15行目
テストスイートを指定します。phpunit実行時、下記のようにテストスイートを指定できます。

phpunit --testsuite="Application Test Suite"

テストスイートを利用することで、すべてのテストを行わずに、カテゴライズされたテストのみ実行できます。

16~20行目
コードカバレッジの対象を指定しています。
21~26行目
定数を指定しています。これにより、アプリケーションはtesting環境での実行であると認識することができます。

tests/TestCase.php

通常、phpでテストを行う際、「PHPUnit_Framework_TestCaseクラス」を継承させてテストクラスを作成しますが、Laravelでは、「TestCaseクラス」を継承させてテストを作成します。

「TestCaseクラス」は、「PHPUnit_Framework_TestCaseクラス」を継承しており、Laravelでテストを行う上で便利な機能を持っています。

tests/TestCase.php
<?php

abstract class TestCase extends Illuminate\Foundation\Testing\TestCase
{
    /**
     * The base URL to use while testing the application.
     *
     * @var string
     */
    protected $baseUrl = 'http://localhost';

    /**
     * Creates the application.
     *
     * @return \Illuminate\Foundation\Application
     */
    public function createApplication()
    {
        $app = require __DIR__.'/../bootstrap/app.php';

        $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

        return $app;
    }
}
10行目
テストするときのベースURLです。ビルトインサーバを立ち上げて開発している場合、「http://localhost:8000」に変更する必要があるかと思います。

継承の関係により、「TestCaseクラス」を継承させたクラスでsetUp()を書く際は、parent::setUpが必要です。

public function setUp(){
    parent::setUp();

}

database/factories/ModelFactory.php

モデルファクトリの定義を行うファイルです。

テストファイル作成

artisanコマンドで作成できます。例を示します。

php artisan make:test Functional/AuthControllerTest

テストの実行

# ファイル指定
./vendor/bin/phpunit tests/Functional/AuthControllerTest.php

# フォルダ指定
./vendor/bin/phpunit tests/Functional

# テストスイート指定
./vendor/bin/phpunit --testsuite="Application Test Suite"

# 全テスト実行
./vendor/bin/phpunit

ユニットテスト(単体テスト)

DBや他のクラスなどの外部依存がないテストになります。テストは順番に依存しないように気を付けてください。

外部依存をなくすために、他のクラスなどの処理はスタブやモックにします。Laravelには、モックをサポートするために「Mockery」がデフォルトでcomposer.jsonに指定されています。

Mockeryの使い方

簡単な使用例
http://docs.mockery.io/en/latest/getting_started/simple_example.html」で紹介されているソースです。

テスト対象ソース

class Temperature
{

    public function __construct($service)
    {
        $this->_service = $service;
    }

    public function average()
    {
        $total = 0;
        for ($i=0;$i<3;$i++) {
            $total += $this->_service->readTemp();
        }
        return $total/3;
    }

}

__constructで外部からモックを注入できるようにしています。

テストソース

use \Mockery as m;

class TemperatureTest extends PHPUnit_Framework_TestCase
{

    public function tearDown()
    {
        m::close();
    }

    public function testGetsAverageTemperatureFromThreeServiceReadings()
    {
        $service = m::mock('service');
        $service->shouldReceive('readTemp')->times(3)->andReturn(10, 12, 14);

        $temperature = new Temperature($service);

        $this->assertEquals(12, $temperature->average());
    }

}
13行目
モックを生成しています。
14行目
readTempメソッドが3回呼ばれる振舞いをすることを指定しています。また、戻り値も指定しています。

呼ばれ方の指定

// methodが(1,2)を引数に1回呼ばれることを期待。呼ばれた際にはfalseを戻り値として返す。
$mock->shouldReceive('method')->with(1, 2)->once()->andReturn(false);

// methodが5回呼び出されることを期待。
$mock->shouldReceive('method')->times(5);

// methodが最低でも2回呼び出されることを期待。
$mock->shouldReceive('method')->atLeast()->times(2);

Laravelの全ファサードはFacadeクラスを継承しています。Facadeクラス上でMockeryが統合されいてるため、Cache,Config,File,Hash,Mailなどのファサードでモックに切り替えることができます。

public function testXxx()
{
    Cache::shouldReceive('get')
                ->once()
                ->with('xxx')
                ->andReturn('yyy');

    // メソッド呼び出し(内部でCacheファサードを利用している箇所がモックに切り替わる)
}

Mockeryの詳しい使い方は以下サイトで確認できます。
http://docs.mockery.io/en/latest/
Mockery 0.8.0 日本語ドキュメント

結合テスト

モデルのクエリーテストなどはDBを利用した結合テストを行います。

DBを利用した結合テストを行う上で必要な知識(モデルファクトリやDBの利用方法)について紹介します。

DB変更

テストのときだけDBを変えたい場合の方法です。

.envでDB_CONNECTIONにmysqlを指定している。
テスト時には、sqliteを利用したい。

こういった場合、phpunit.xmlに以下のようにDB_CONNECTIONの行を追記します。

<php>
                      :
    <env name="DB_CONNECTION" value="sqlite" />
</php>

モデルファクトリ

ファクトリーの定義
「database/factories/ModelFactory.php」でファクトリーの定義を行います。

$factory->define(App\User::class, function (Faker\Generator $faker) {
    static $password;

    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => $password ?: $password = bcrypt('secret'),
        'remember_token' => str_random(10),
    ];
});

クロージャの引数にFaker(https://github.com/fzaninotto/Faker)を渡してます。Fakerはデフォルトでcomposer.jsonに指定されており、ランダムデータを生成するために利用します。

ファクトリーの使用
テスト上で、factory関数を利用することでモデルインスタンスを生成できます。

public function testXxx()
{
    // モデル生成のみ。データベースには保存しない。
    $user = factory(App\User::class)->make();

    // モデル生成して、データベースにも保存する。
    $user = factory(App\User::class)->create();

    // name属性をオーバーライド。他の属性は定義された通り。
    $user = factory(App\User::class)->make([
        'name' => 'James',
    ]);
}

データベースの利用

DatabaseMigrationsトレイトを使うだけで、テスト前にマイグレーションが実行され、テスト後にロールバックが実行されます。

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use DatabaseMigrations;

}

seeInDatabaseメソッド

テスト用ヘルパーメソッドのseeInDatabaseメソッドでDB上にレコードが存在するか確認できます。

// usersテーブルのemailフィールドに値が
// sally@example.comであるレコードが存在することを確認
$this->seeInDatabase('users', ['email' => 'sally@example.com']);

※DB関連の詳しいことについては以下で確認できます。
https://readouble.com/laravel/5.3/ja/database-testing.html

機能テスト

主にコントローラメソッドの動作テストになります。

次のことを確認します。

どういった種別のユーザー(管理者、認証済みユーザー、オーナー)が、
特定のURLに、
特定のHTTPメソッド(GET,POST,PUT,DELETE)を実行した際、
どういった振る舞いをして、
どういった変数をビューに渡し、
どういったレスポンスを返すのか

クロールの利用

フロントでJSを利用していない箇所については、クロールで画面の機能テストが行えます。

下記ソースでは、visitメソッドで指定されたURLに対するGETリクエストを作成してます。メソッドチェーンでリクエストに対する評価を行っています。

$this->visit('/')
     ->seePageIs('/')     // '/' のページであること
     ->see('Laravel 5')   // 'Laravel 5' という文字列が含まれていること 
     ->dontSee('Rails');  // 'Rails' という文字列が含まれていないこと

click、type、select、check、uncheck、attach、pressなどのメソッドを利用することで、リンクのクリック、フォーム入力などの操作もできます。

actingAsメソッドで認証済みユーザーを指定できます。

$user = factory(App\User::class)->create();

$this->actingAs($user)
     ->withSession(['foo' => 'bar'])
     ->visit('/')
     ->see('Hello, '.$user->name);

WebAPI JSONのテスト

// seeJsonで指定した配列が、レスポンスに含まれていることを確認
$this->json('POST', '/user', ['name' => 'Sally'])
     ->seeJson([
         'created' => true,
     ]);

// seeJsonEqualsで指定した配列が、レスポンスと一致することを確認
$this->json('POST', '/user', ['name' => 'Sally'])
     ->seeJsonEquals([
         'created' => true,
     ]);

※より詳しい情報は以下リンクで確認できます。
https://readouble.com/laravel/5.3/ja/application-testing.html

スポンサーリンク