При работе с приложениями 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()); } }
Такой подход гарантирует, что ваши тесты более точно отражают, как задания будут обрабатываться в реальном мире.
Ту же стратегию можно применить, когда контроллер отправляет задание в очередь или когда в очереди стоит прослушиватель событий.
Как всегда, корректируйте эти методы в соответствии с вашим проектом и командой.
Мне бы хотелось услышать ваше мнение!
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3