使用 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 的队列系统,根据你的 Workers 配置,发生异常时会重试作业,如果重试次数超过,作业将被标记为失败。
那么,如果文件不存在会发生什么?您需要通过验证输入并在必要时引发异常来处理此类边缘情况。
以下是您在工作中可以如何处理此问题的方法:
/* 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()); } }
这种方法可确保您的测试更准确地反映现实世界中作业的处理方式。
当控制器将作业分派到队列或事件侦听器排队时,可以应用相同的策略。
与往常一样,调整这些实践以适合您的项目和团队。
我很想听听您的想法!
免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。
Copyright© 2022 湘ICP备2022001581号-3