在使用 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()); } }
這種方法可確保您的測驗更準確地反映現實世界中作業的處理方式。
當控制器將作業分派到佇列或事件偵聽器排隊時,可以套用相同的策略。
像往常一樣,調整這些實踐以適合您的專案和團隊。
我很想聽聽你的想法!
以上是在 Laravel 測試排隊作業的技巧的詳細內容。更多資訊請關注PHP中文網其他相關文章!