Fippiyのプログラム学習内容アウトプットBlog

日々の学習内容をアウトプットして振り返りを実施する。

Laravel開発、テスト環境を整える

書籍管理アプリの基本とする機能は完成しました。

修正や追加実装をしたいところですが、テストを行い現状のアプリに問題ないか検証していくことにしました。

まずは、ユニットテストを実施していきます。

ビュー表示やアクションなど単体のテストを実施することで、個々の動作を検証して問題ないことを確認するテストです。

ユニットテストを実施するための環境を整え問題なく動作することを確認していきます。

今回の目的

テスト環境を整備し、テストが行える状態にする。

なぜやるか

不具合・バグを極小化するために、テストコードによる正常動作を確認できるようにするためのテスト環境を用意するため。

やりたいこと

テストが実施できる環境設定をする

基本的なテストコードを書く

テストが正常に完了することを確認する

アプリに対してのテストを実施してみる

やったこと 

  • テスト環境を整える手順を確認する
  • テスト用DBを作成する
  • ダミーレコードを確認する
  • テストコマンドを使用する
  • テストコードを書く
  • テスト用DBを使えるようにする
  • テストレコード比較をする

実施内容

テスト環境を整える

手順の確認

テストを行うにあたり、まずテスト実施環境を整えます。

まずは、Laravelを初めに学習した本を参考に準備していきます。

www.amazon.co.jp

必要な手順としては

  • テスト用DBを作成する
  • ダミーレコードを用意する
  • テストコードを書く
  • テストを実行する

このあたりが準備できれば、テスト環境としては整いそうです。これらを実施します。

 

テスト用DBを作成する

DBへの書き込み等をテストするには、実際にDBを用意して動作させることになります。

しかし、現状用意しているDBは本番で使用するDBのみとなっています。

ここに直接記述してテスト…することはできるようですが、本番環境に不要なデータが追加されたり必要なデータが消去される可能性もあります。

そこで、テスト専用のDBを用意して、本番用とは切り離して実装します。

 

まずは、専用DBを作成します。

アプリのDBにはMySQLを使用しており、開発時のデータ確認にはSequelProというアプリを利用しています。

今回はSequelPro上でテスト用のDBを作成しました。

 

作成したテスト用DBをテスト用DBとして設定する

ユニットテストの設定ファイルがあるので、こちらに作成したDBの設定を追記します。

# ~/phpunit.xml

<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="MAIL_DRIVER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="DB_DATABASE" value="book-property-management_test"/>
</php>

アプリ名_testという名前にしてDBを追加しました。

※なお、webで調べてみたところ<env 〜>タグとなっていたのですが、本アプリではserverというタグになっていたので、こちらで追記しています。

 

ダミーレコードを準備する

続いてダミーレコードを確認します。

# ~/database/factories/UserFactory.php

<?php

use App\User;
use Illuminate\Support\Str;
use Faker\Generator as Faker;

$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
});

Laravelアプリ作成時にデフォルトで作成済みのUser用データです。

マイグレーションファイルも最初から作成されていたように、こちらも最初からあります。

今回はこのまま利用することにしました。

 

テストコードを用意する 

後はテストコードを書いていきます。

Userのダミーデータがありましたので、Userをテストするためのファイルを準備することにしました。

テスト用の雛形はコマンドで用意できます。

$ php artisan make:test UserTest

作成されたファイルはこちらです。

# ~tests/Feature/UserTest.php

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class UserTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
public function testExample()
{
$response = $this->get('/');

$response->assertStatus(200);
}
}

このなかにテストを書いていくことになります。

が、まずは動作確認なので、このまままずはテストを実施してみます。 

テスト実施コマンドを実施。

$ vendor/bin/phpunit

PHPUnit 7.5.7 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 500 ms, Memory: 16.00 MB

OK (3 tests, 3 assertions) 

テストOKとなり、正常に完了しました。

 

テストコードを書く

基本的なコードを書く

User用のテストファイルを作成しましたが、初めからあるテストファイルに基本的なテストコードを書いてみました。

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest()
{
// code // 判定
$this->assertTrue(true); // 要素true
$this->assertFalse(false); // 要素false

$this->assertEquals(100, 100); // 値が同じ
$this->assertNotEquals(100, 101); // 値が同じではない


$arr = ;
$this->assertEmpty($arr); // 要素が空orNull
$this->assertNull(null); // 要素が空orNull
$arr = [1,2,3];
$this->assertNotEmpty($arr); // 要素が空orNullではない
$this->assertNotNull($arr); // 要素が空orNull

$msg = "Hello";
$this->assertEquals('Hello', $msg); //要素が同じ

$n = random_int(0, 99);
$this->assertLessThan(100, $n); // 100 > $n
$this->assertLessThanorEqual(100, 100); // 100 >= $n
$this->assertGreaterThan($n, 100); // 100 < $n
$this->assertGreaterThanorEqual(100, 100); // 100 <= $n

$this->assertStringStartsWith('a','abcdef'); // aで始まる文字
$this->assertStringEndsWith('f','abcdef'); // fで終わる文字

$response = $this->get('/'); // ルートにアクセス
$response->assertStatus(200); // 200ステータスであること
}
}

この内容でテストを実施します。

$ vendor/bin/phpunit

PHPUnit 7.5.7 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 131 ms, Memory: 16.00 MB

OK (3 tests, 20 assertions)

全てのテストが通りました。

このようにテストしたい内容を記述して全てがOKとなれば、テストコードとして問題なく動作していることが確認できます。

テストコード用の記述は今までのアプリ作成とは異なるコードがあるので、こちらも繰り返し実施して学習する必要がありそうですね…。

 

アプリ向けのテストコードを書く

テストが実施できるようになったので、今回のアプリ向けのコードを少し用意してアプリのテストができるかを確認します。

先程User用のテストファイルを作成していますので、Userページ向けのテストを実施します。

 

まずは改めてUserテスト用ファイルを確認。 

# ~/tests/Feature/UserTest.php

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class UserTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
public function testExample()
{
$response = $this->get('/');

$response->assertStatus(200);
}
}

コマンドによるファイル生成をした時点の内容です。

こちらにUserページ用のコードを書いていきます。

# ~/tests/Feature/UserTest.php

public function testExample()
{
$this->assertTrue(true);

$response = $this->get('/'); // ルートにアクセス
$response->assertStatus(200); // 200ステータスであること

$response = $this->get('/user'); // ~/userにアクセス
$response->assertStatus(302); // 302ステータスであること

$user = factory(User::class)->create(); // User作成
$response = $this->actingAs($user)->get('/user'); // 作成ユーザーでログインして~/userにアクセス
$response->assertStatus(200); // 200ステータスであること

$response = $this->get('/no_route'); // ページのないアドレスへアクセス
$response->assertStatus(404); // 404ステータスであること
}

webページアクセスに関係するコードを記述しました。

ユーザーを作成してログインするコードもありますので、ユーザーテーブルにデータを保存するといった内容もあります。

 

この状態でテストするもエラーとなりました。

There was 1 error:

1) Tests\Feature\UserTest::testExample

Illuminate\Database\QueryException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'book-property-management_test.users' doesn't exist (SQL: insert into `users` (`name`, `email`, `email_verified_at`, `password`, `remember_token`, `updated_at`, `created_at`) values (Ada Hauck, otto67@example.net, 2019-04-23 22:16:01, $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi, kiGTVs58LO, 2019-04-23 22:16:01, 2019-04-23 22:16:01))

 

SQLの処理が失敗しているようです。 

テスト用DBは作成していましたが、テーブルがまだ作成されていないので書き込むテーブルがないようです。

 

テスト用DBのテーブルを用意する

そこでテスト用DBをマイグレーションすることにしました。

※後ほど自動化しました(後述)まず手動でマイグレーションしてます。 

 

テスト用DBのマイグレーションを実施するにあたっては、こちらを参考にさせて頂きました、ありがとうございます。

qiita.com

テスト用のマイグレーション設定をし、設定データを利用してマイグレーションできるようです。

configにテスト用のmysql設定を追加します。

# ~/config/database.php

'mysql_test' => [
'driver' => 'mysql',
'host' => env('DB_HOST', ''),
'port' => env('DB_PORT', ''),
'database' => env('DB_DATABASE_TEST', ''),
'username' => env('DB_USERNAME', ''),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : ,
],

実際に運用する側と設定はほとんど同じです。

テスト用と分かるように"_test"という名前を付加しています。

 

テスト用DB設定を利用してマイグレーションコマンドを実行します。

$ php artisan migrate --database=mysql_test

 

これでテスト用DBにテーブルが用意できたので、改めてテストを実施します。

$ vendor/bin/phpunit

PHPUnit 7.5.7 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 217 ms, Memory: 18.00 MB

OK (3 tests, 24 assertions)

テーブルを用意することで、作成したユーザー情報を保存し、正常動作しました。

 

テスト用DBマイグレーションを自動化する

テーブルを用意することで、テスト環境としては整ったのですが、テーブル作成を調査しているうちに自動化できる方法がありましたので、こちらを実装することにしました。

 

自動マイグレーションは、こちらを参考にさせて頂きました、ありがとうございます。  

readouble.com

自動化するための設定をテストコードで指定します。

# ~/tests/Feature/UserTest.php

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use App\User;

class UserTest extends TestCase
{
use DatabaseMigrations;
/**
* A basic feature test example.
*
* @return void
*/
public function testExample()
{
$this->assertTrue(true);
〜略〜

DatabaseMigrationsを追記しました。

この機能を利用することで、テスト開始時にmigrationしてテストが終わるとrollbackされます。

テストする時だけDBを作成するわけです。

テストが終わるとロールバックでDB自体を削除するので、再度テストするときに前回テスト時の古いデータを考慮する必要はなくなります。

この時参照するDBは初めに設定した<server >タグで設定したDBとなります。 

 

自動化によって手動マイグレーションで使用していたconifg設定はいらなくなったので削除しました。

 

DBレコード比較をする

この記事の最後としてDBレコードが一致していることの確認を実施しました。

# ~/tests/Feature/UserTest.php

public function testDatabase()
{
factory(User::class)->create([
'name' => 'AAA',
'email' => 'BBB@CCC.COM',
'password' => 'ABCABC',
]); // DBに配列で指定したユーザーを生成
factory(User::class, 10)->create(); // UserFactory指定のユーザーを10レコード作成

$this->assertDatabaseHas('users', [
'name' => 'AAA',
'email' => 'BBB@CCC.COM',
'password' => 'ABCABC',
]); //DBに配列で指定したユーザーがいること
}

ユーザーの新規作成を配列に記載した内容で個別に作成しています。

その後にダミーデータを利用したユーザーを10レコード作成しました。

数を指定することでまとめて作成するといったこともできます。

 

そし最後にて、usersテーブルに対して指定ユーザーがいることを確認しています。

11ユーザーいる中から初めに作成したユーザーが同一ユーザーであるため試験としては通る…といった内容です。

 

以上で、テスト環境を整えることから、実際に一部のテストコードを記述して試験が通ることまでを確認しました。

 

次回から、より詳細なコードを記述して、ユニットテストをしていきます。