Laravel アプリケーションを使用する場合、コマンドが負荷の高いタスクを実行する必要があるシナリオに遭遇するのが一般的です。メインプロセスのブロックを避けるために、キューで処理できるジョブにタスクをオフロードすることを決定することもできます。
例を見てみましょう。コマンド app:import-users が大きな CSV ファイルを読み取り、エントリごとにユーザーを作成する必要があると想像してください。コマンドは次のようになります:
/* ImportUsersCommand.php */ namespace App\Console\Commands; /*...*/ class ImportUsersCommand extends Command { protected $signature = 'app:import-users'; public function handle() { dispatch(new ImportUsersJob()); $this->line('Users imported successfully.'); $this->line('There are: ' . User::count(). ' Users.'); } }
この例では、コマンドはファイルの読み取りとユーザーの作成を処理するジョブをディスパッチします。 ImportUsersJob.php は次のようになります:
/* ImportUsersJob.php */ namespace App\Jobs; /*...*/ class ImportUsersJob implements ShouldQueue { public function handle(FileReader $reader): void { foreach($reader->read('users.csv') as $data) { User::create([ 'name' => $data['name'], 'email' => $data['email'], ]); } } }
この機能をテストする場合、コマンドの一般的なテストは次のようになります。
/* ImportUsersCommandTest.php */ namespace Tests\Feature; /*...*/ class ImportUsersCommandTest extends TestCase { use RefreshDatabase; public function test_it_processes_the_file(): void { Storage::fake('local')->put('users.csv', "..."); $this->artisan('app:import-users') ->expectsOutput('Users imported successfully.') ->expectsOutput('There are: 10 Users.') ->assertSuccessful(); $this->assertDatabaseCount('users', 10); } }
一見すると、このテストは完璧に機能しているように見えます。テスト スイートを実行すると、成功した結果が表示されます:
ただし、実際の環境で app:import-users コマンドを実行すると、予期しない結果が得られる可能性があります。
ご覧のとおり、コマンド出力はデータベースにユーザーが 0 人しかいないことを示しています。では、なぜこのようなことが起こるのでしょうか?
その理由は、ジョブがキューにディスパッチされるため、コマンドの実行と同期して実行されないためです。ユーザーは、後でキューがジョブを処理するときにのみ作成されます。
テスト スイートはデフォルトで同期キュー ドライバーを使用します。つまり、ジョブはテスト中に同期的に処理されます。その結果、ジョブはすぐに実行され、すべてが期待どおりに機能していることがわかります。
この動作はテスト環境では許容されますが、実際の結果は運用環境の QUEUE_CONNECTION 構成に依存することを認識することが重要です。また、プロジェクトの要件を考慮すると、ジョブが非同期キューで処理されることがわかっているかもしれません。
この違いを理解したら、「偽陽性」を回避するためにテストを改善したくなるかもしれません。
まず、ジョブが同期的に処理されるか非同期的に処理されるかに関係なく、コマンドが実際にジョブをディスパッチすることを確認することが重要です。それをテストする方法は次のとおりです:
/* ImportUsersCommandTest.php */ namespace Tests\Feature; /*...*/ class ImportUsersCommandTest extends TestCase { public function test_it_dispatches_the_job(): void { Queue:fake(); $this->artisan('app:import-users') ->expectsOutput('Process has been queued.') ->assertSuccessful(); Queue::assertPushed(ImportUsersJob::class); } }
ジョブがディスパッチされたことを確認したら、別のテストでジョブによって実行される実際の作業をテストできます。ジョブのテストを構成する方法は次のとおりです:
/* ImportUsersJobTest.php */ namespace Tests\Feature; /*...*/ class ImportUsersJobTest extends TestCase { use refreshDatabase; public function test_it_processes_the_file() { Storage::fake('local')->put('users.csv', "..."); app()->call([new ImportUsersJob(), 'handle']); $this->assertDatabaseCount('users', 10); } }
これにより、ジョブがキューによって処理されるか同期的に処理されるかに関係なく、必要な作業が確実に実行されます。
現実世界と同様に、特殊なケースが発生する可能性があるため、それらに備えておく必要があります。
Laravel のキュー システムは、ワーカーの設定に従って、例外が発生するとジョブを再試行します。再試行回数を超えると、ジョブは失敗としてマークされます。
それでは、ファイルが存在しない場合はどうなるでしょうか?入力を検証し、必要に応じて例外をスローすることで、このようなエッジケースに対処する必要があります。
仕事でこれに対処する方法は次のとおりです:
/* ImportUsersJobTest.php */ namespace App\Jobs; /*...*/ class ImportUsersJob implements ShouldQueue { use Queueable; public function handle(FileReader $reader): void { if(!Storage::disk('local')->exists('users.csv')){ throw new Exception('The users.csv file doesn\'t exist.') } foreach($reader->read('users.csv') as $data) { User::create([ 'name' => $data['name'], 'email' => $data['email'], ]); } } }
このシナリオをテストする方法は次のとおりです:
/* ImportUsersJobTest.php */ namespace Tests\Feature; /*...*/ class ImportUsersJobTest extends TestCase { use refreshDatabase; /*...*/ public function test_it_fails_when_file_doesnt_exist(): void { Storage::fake('local'); $this->expectException(Exception::class); $this->expectExceptionMessage('The users.csv file doesn\'t exist.'); dispatch(new ImportUsersJob()); } }
このアプローチにより、テストは現実世界でジョブがどのように処理されるかをより正確に反映するようになります。
コントローラーがジョブをキューにディスパッチする場合、またはイベント リスナーがキューに登録されている場合にも、同じ戦略を適用できます。
いつものように、プロジェクトやチームに合わせてこれらのプラクティスを調整してください。
ぜひご意見をお聞かせください!
以上がLaravel でキューに入れられたジョブをテストするためのヒントの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。