Ao trabalhar com aplicações Laravel, é comum encontrar cenários onde um comando precisa executar uma tarefa cara. Para evitar o bloqueio do processo principal, você pode decidir transferir a tarefa para um trabalho que possa ser processado por uma fila.
Vejamos um exemplo. Imagine que o comando app:import-users precisa ler um arquivo CSV grande e criar um usuário para cada entrada. Esta é a aparência do comando:
/* 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.'); } }
Neste exemplo, o comando despacha um job para tratar da leitura do arquivo e da criação de usuários. Esta é a aparência do 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'], ]); } } }
Ao testar esse recurso, um teste típico para o comando pode ser assim:
/* 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); } }
À primeira vista, este teste parece funcionar perfeitamente. A execução do conjunto de testes mostra um resultado bem-sucedido:
No entanto, ao executar o comando app:import-users em um ambiente real, você pode obter um resultado inesperado:
Como você pode ver, a saída do comando indica que há 0 usuários no banco de dados. Então, por que isso acontece?
O motivo é que o trabalho é despachado para uma fila, portanto não é executado de forma síncrona com a execução do comando. Os usuários serão criados somente quando a fila processar o trabalho posteriormente.
O conjunto de testes usa o driver de fila de sincronização por padrão, o que significa que os trabalhos são processados de forma síncrona durante o teste. Como resultado, o job é executado imediatamente, dando a ideia de que tudo funciona conforme o esperado.
Embora esse comportamento seja aceitável no ambiente de teste, é importante reconhecer que os resultados do mundo real dependem da configuração QUEUE_CONNECTION em seu ambiente de produção. E dados os requisitos do seu projeto, você deve saber que o trabalho será processado em uma fila assíncrona.
Depois de estar ciente dessa distinção, você pode querer melhorar seus testes para evitar “falsos positivos”.
Primeiro, é importante verificar se o comando realmente despacha o trabalho, independentemente de o trabalho ser processado de forma síncrona ou assíncrona. Veja como testar isso:
/* 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); } }
Depois de confirmar que o trabalho foi enviado, você poderá testar o trabalho real executado pelo trabalho em um teste separado. Veja como você pode estruturar o teste para o trabalho:
/* 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); } }
Isso garante que o trabalho execute o trabalho necessário, independentemente de ser processado por uma fila ou de forma síncrona.
Como na vida real, casos extremos podem acontecer e você deve estar preparado para eles.
O sistema de filas do Laravel, de acordo com a configuração dos seus trabalhadores, tentará novamente os trabalhos quando ocorrer uma exceção e, se as tentativas forem excedidas, o trabalho será marcado como falhado.
Então, o que acontece se o arquivo não existir? Você precisa lidar com esses casos extremos validando entradas e lançando exceções quando necessário.
Veja como você pode lidar com isso em seu trabalho:
/* 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'], ]); } } }
Veja como você testaria esse cenário:
/* 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()); } }
Essa abordagem garante que seus testes reflitam com mais precisão como os trabalhos serão processados no mundo real.
A mesma estratégia pode ser aplicada quando um controlador despacha um trabalho para uma fila ou onde um ouvinte de evento está na fila.
Como sempre, ajuste essas práticas para se adequarem ao seu projeto e equipe.
Eu adoraria ouvir sua opinião!
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3