環境
OS: Windows 10
PHP: 8.1.2
Laravel Framework: 8.81.0
docker-compose: 1.29.2
PHPUnit: 9.5.10
MySQL: 8.0
Laravel Sail
を使用して、Laravelプロジェクトを立ち上げています。
プロジェクトを立ち上げる部分は、こちらのドキュメントでご確認ください。
RefreshDatabase
DBの状態に影響されずテストする
use RefreshDatabase;
をしてあげると、
テストを実行する際に前後のDBの状態を考える必要がなくなります。
各テスト毎にDBをリフレッシュするため、まっさらな状態にしてくれるので、
DBの状態を確認するアサーションである
$this->assertDatabaseCount(...);
$this->assertDatabaseHas(...);
$this->assertDatabaseMissing(...);
あたりが扱いやすくなります。
そして、テストによって追加されてしまうデータも残らないので安心です。
では、実際に動作を見るためにテストを作成しましょう。
テストの作成
まずは、テストの作成のために以下のコマンドを叩きます。
$ ./vendor/bin/sail artisan make:test Model/UserTest
そして、作成されたファイルを以下のように修正します。
<?php
namespace Tests\Feature\Model;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase;
const TABLE_NAME = 'users';
protected function setUp(): void
{
parent::setUp();
}
/**
* @return int
* @throws \Exception
*/
private function factory(): int
{
// 1~9のランダムな数値を取得
$count = random_int(min: 1, max: 9);
// $countの数だけ、新規にUserを作成する
User::factory($count)->create();
return $count;
}
/**
* @test
* @group Model
* @group User
*
* @return void
* @throws \Exception
*/
public function 全件取得(): void
{
$count = $this->factory();
// usersテーブルに$countの数だけデータが挿入されているか
$this->assertDatabaseCount(table: self::TABLE_NAME, count: $count);
$result = User::all();
// 全件取得できているか
$this->assertCount(expectedCount: $count, haystack: $result);
}
}
さて、テストが作成できましたので、
これを動作させてみましょう。
テストの実行
テストの実行のためには、以下のコマンドを叩きます。
$ ./vendor/bin/sail test --group=Model
今回のテストには$this->assertDatabaseCount(...);
が含まれています。
もし、DBの状態がリフレッシュされているならば、連続で実行しても問題ないはずです。
試しに3回ほど実行してみましょう!
$ ./vendor/bin/sail test --group=Model
PASS Tests\Feature\Model\UserTest
✓ 全件取得
Tests: 1 passed
Time: 0.33s
$ ./vendor/bin/sail test --group=Model
PASS Tests\Feature\Model\UserTest
✓ 全件取得
Tests: 1 passed
Time: 0.30s
$ ./vendor/bin/sail test --group=Model
PASS Tests\Feature\Model\UserTest
✓ 全件取得
Tests: 1 passed
Time: 0.29s
問題なくPASSしていますね!
これでDBの状態に影響されずテストができることが分かります。
テスト後のDBの状態を確認
次にテストによって追加されてしまうデータが残っていないことを確認します。
確認方法は何でも良いのですが、今回はできる限り簡潔に画面に表示します。
以下の実装を行いましょう。
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
$users = \App\Models\User::all();
dd($users);
});
あとは、http://localhost/に接続すれば・・・
出ました!!
#items: []
ということは、何のデータも入っていないことなので、
これでテストによって追加されてしまうデータが残っていないことが分かりました。
本当に影響がないか確認
テストがデータに影響しないこともデータが残ってないことも分かりました。
では、本当に何も影響がないのでしょうか?
それを確認するためにダミーデータを入れてからテストを実行してみましょう。
ダミーデータの作成
まずは、ダミーデータを作成するためにシーダーを作成する必要がありますので、
シーダーを作成するために以下のコマンドを叩きます。
$ ./vendor/bin/sail artisan make:seeder UserSeeder
シーダーの作成ができたら、ダミーデータを作成するために実装しましょう。
以下の修正を加えればOKです。
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
User::factory(10)->create();
}
}
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
$this->call([
UserSeeder::class,
]);
}
}
これでダミーデータを作成する準備が整ったので、以下のコマンドを叩きましょう。
$ ./vendor/bin/sail artisan db:seed
作成しているかどうか、http://localhost/に接続して確認してみます。
#items: array:10 [▶]
となっているので作成できていますね。
結果は・・・
では、テストを実行してから確認してみましょう。
$ ./vendor/bin/sail test --group=Model
そして、http://localhost/に接続・・・
き、消えてる・・・。
ということで、汚染しない代わりに綺麗に全てを消してしまうのです。
解決方法
テスト実行の度にダミーデータを挿入するのも手間がかかりますし、
手動で追加したデータなどは消えてしまいます。
これでは、根本的な解決になりません。
根本的な解決方法を考えるなら
テストのときは違うDB環境を使用すればよいのです。
sqliteを使用する
設定は何もこだわらなければ、とても簡単です。
以下のように、コメントアウトを外すだけです。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>
これで設定完了です。
テスト実行(sqlite)
先程と同じようにダミーデータの作成し、テストを実行してみます。
$ ./vendor/bin/sail artisan db:seed
$ ./vendor/bin/sail test --group=Model
そして、http://localhost/に接続・・・
やりました!
ダミーデータが残ったままです!
テストで違うDB環境を使用する
簡単な設定でしたが、残念ながら
外部キー制約などがあると、sqliteを使うことはできません・・・。
そんなときは、大人しく別のDBを用意する方法が良いでしょう。
.env.testing
まず、.env.testing
を作成します。
一番楽なのは、.env
を複製してリネームすることです。
重要なのは、
APP_KEY
にキーがあること、DB_HOST
が.env
と異なることです。
今回は、DB_HOST
をmysql.test
とします。
...
APP_KEY=(キーが入っている)
...
DB_CONNECTION=mysql
DB_HOST=mysql.test
DB_PORT=(.envと一緒)
DB_DATABASE=(.envと一緒)
DB_USERNAME=(.envと一緒)
DB_PASSWORD=(.envと一緒)
...
APP_KEY
にキーが入っていない
その場合は、以下のコマンドで生成しましょう。
$ ./vendor/bin/sail artisan key:generate --env=testing
docker-compose.yml
先程、.env.testing
で追加したDB_HOST
を追加します。
今回はmysql.test
ですね。
mysql.test:
image: 'mysql/mysql-server:8.0'
environment:
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
MYSQL_ROOT_HOST: "%"
MYSQL_DATABASE: '${DB_DATABASE}'
MYSQL_USER: '${DB_USERNAME}'
MYSQL_PASSWORD: '${DB_PASSWORD}'
MYSQL_ALLOW_EMPTY_PASSWORD: 1
networks:
- sail
テスト用に使用するだけなので、
ports、volumes、healthcheckなどは不要です。
追加した後は、sail
を再度立ち上げましょう!
$ ./vendor/bin/sail up -d
テスト実行(別DB)
テストを実行してみます。
$ ./vendor/bin/sail artisan db:seed
$ ./vendor/bin/sail test --group=Model
そして、http://localhost/に接続・・・
こちらでも問題なく残っていますね!
まとめ
ということで、上記の手順によりDBの汚染を防ぐことができます。
テストを記述するのはとても大切だと思っているので、
備忘録ではありますが、この記事があなたの役に立てば嬉しいです。