はじめに
本記事をご覧いただきありがとうございます。東京でバックエンドエンジニアとして働いている大野です。今回は初心者や経験の浅いエンジニアがよくやってしまう(?)テスト実行時にデータベースを空にしてします事件について対策を書いていきます。私も過去に1度やってしまったので自戒を込め、実施した対応を備忘録として残します。扱う内容はLaravelです。参考になりますと幸いです。また、誤った箇所がございましたらご教示いただけますと幸いです。
テスト実行にあたっての前提
本記事では以下の環境を使用しています。私は普段、ローカルでsailコマンドを扱っているので、普段sailコマンドを使っていない方は以降で出てくるコマンドの「sail」の部分を「php」に読み替えてください。(sail artisan 〜 ⇨ php artisan 〜)
- Laravel11
- PHP Unit
- Mac M3
- docker compose(Laravel sail)
テストは本番環境では実行しないこと!!
テストコードを書いたことあるかたであればLaravelに限らず当たり前かと思いますが、初めてテストを書く方にとっては知らないことかと思うので、もしご存じない方は肝に銘じておきましょう。
テストで使用するDBの考え方
Laravelの場合、UnitテストとFeatureテストが初めからフォルダが用意されています。どちらも以下のコマンドから始まる実行方法です。(以下コマンドをそのまま実行すると全てのFeatureテストが実行されます)
sail artisan test
テストについての詳しいやり方はこちらを参考にしてみてください。
Laravelのテストではテスト実行の都度、DBの中身を空にすることが多いです。以下にテストのサンプルをご覧ください。
<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ProfileTest extends TestCase
{
use RefreshDatabase;
public function test_全ユーザーの表示確認(): void
{
$users = User::factory()->count(3)->create();
$response = $this->get('/');
$response->assertStatus(200);
foreach ($users as $user) {
$response->assertSee($user->name);
}
}
public function test_特定のユーザーの表示確認(): void
{
$user = User::factory()->create();
$response = $this->get('/profile/' . $user->id);
$response->assertStatus(200);
$response->assertSee($user->name);
}
}
全ユーザーの表示確認と特定ユーザーの表示確認と2ケース実施しているのですが、use RefreshDatabase
の記述をすることによって各テスト実行後にDBの中身を空にしています。
このテストで使用されるDBは開発で使用しているDBではなく、デフォルトだとtestingという名前のデータベースに接続されています。なので開発で使用しているDBに影響なくDBテストができます!
他にも設定があるのですが、本題はテストの実行方法ではないので割愛します。Laravelでのテストについてのベースの話は以上にしておきます。
開発で使用しているDBが空になってしまう!
前置きが長くなりましたが、ここからが本題です。私は過去、Featureテスト実行時に開発で使用している方のDBが空になってしまったことがあります。その前からテストは何度も経験していたためなぜ今回だけ?となりました。ローカル環境だったのであまり焦りませんでしたが、これが他の環境だったら恐ろしいですね。つまるところ、テスト用じゃないDBに接続されてしまっていたことが不味かったですね。
結論、よくあるLaravelが普段使用しているDBに接続してしまう原因は以下かなと!
- phpunit.xmlの設定が誤っている。(APP_ENV属性の値とか)
- configがキャッシュされている
私は2つめの「configがキャッシュされている」のが原因でした。laravelでは以下コマンドでconfigのキャッシュを生成できます。
sail artisan config:cache
本番環境だとパフォーマンスを考慮してこれをやりますが、私はローカルの環境でもこれをやっていました。configがキャッシュされているとほかのテストの参照よりもキャッシュが優先されるようです。
解決策
私が実施した解決策はこんな感じです。
- app/bootstrapフォルダにtesting.phpを追加
- phpunit.xmlを編集
app/bootstrapフォルダにtesting.phpを追加
以下のファイルを新規作成
<?php
system('php artisan config:clear');
require_once __DIR__ . '/../vendor/autoload.php';
phpunit.xmlを編集
以下の箇所を書き換えます。
<?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"
+ bootstrap="bootstrap/testing.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_DATABASE" value="testing"/>
<env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>
testing.phpではキャッシュのクリアを実行しています。phpunit.xmlにてそれを呼び出すことによりテスト実行の都度キャッシュがあってもクリアしてからテスト開始するため、確実にテスト用DBへの接続がされます。私はこの方法で解決しました。
終わりに
最後までご覧いただきありがとうございました!テストでDBを吹っ飛ばしちゃうのだいぶ焦ると思いますが、そんな方々の参考になればと思い執筆しました。私がこの経験をした時はconfigがキャッシュされていることが原因でしたが、そもそもlocalではキャッシュさせる必要ありませんかね?ここら辺弱いので勉強しとこうと思います。
余談ですが、普段私はスタートアップで一人エンジニアをやっています。スタートアップの働き方や一人エンジニアというぶっちゃけ過酷な環境ですが、私のマインドをこの記事で書いていますのでよければ覗いてみてください。これからも頑張ります。