Our Location
304 North Cardinal St.
Dorchester Center, MA 02124
在 Web 應用程式開發中,確保 API 的穩定性和效能至關重要。有時,API 會有並行處理的限制,例如,為了防止資源競爭或資料不一致,同一個 API 在同一時間只能被一個請求呼叫。這種情況下,傳統的循序測試方法可能不足以驗證 API 的行為。這時候,我們需要使用多執行緒測試。
在 Web 應用程式開發中,確保 API 的穩定性和效能至關重要。有時,API 會有並行處理的限制,例如,為了防止資源競爭或資料不一致,同一個 API 在同一時間只能被一個請求呼叫。這種情況下,傳統的循序測試方法可能不足以驗證 API 的行為。這時候,我們需要使用多執行緒測試。
這篇文章將會介紹兩種在 Laravel 中使用 Pest 進行多執行緒測試的方法:
pcntl
擴充套件我們將使用「購物車下單與庫存數」作為範例。假設我們有一個 API 端點 /api/checkout
,它接受 product_id
和 quantity
作為參數,並嘗試下單。為了防止超賣,我們需要確保即使多個請求同時到達,庫存也能正確處理。
pcntl
擴充套件pcntl
(Process Control) 是 PHP 的一個擴充套件,它提供了一系列用於處理 Unix 行程的函式。我們可以使用 pcntl
來建立多個子行程,每個子行程執行一個測試任務,從而模擬並行下單的場景。
範例程式碼:
des\DB;
// 假設我們的 API 端點是 /api/checkout
$apiEndpoint = '/api/checkout';
test('API 應限制並行購買請求 (使用 pcntl)', function () {
$processes = [];
$numRequests = 5; // 模擬 5 個並行請求
$productId = 1;
$initialStock = 10; // 假設產品初始庫存為 10
// 建立測試資料
DB::table('products')->insert([
'id' => $productId,
'stock' => $initialStock,
]);
// 建立多個子行程
for ($i = 0; $i < $numRequests; $i++) {
$pid = pcntl_fork();
if ($pid == -1) {
die("無法 fork 行程");
} elseif ($pid) {
// 父行程:儲存子行程 ID
$processes[] = $pid;
} else {
// 子行程:發送 API 請求
// 使用 Laravel 的 HTTP 測試功能
$response = test()->postJson($apiEndpoint, ['product_id' => $productId, 'quantity' => 1]);
// 這裡我們不直接斷言,而是將結果回傳
// 使用檔案來回傳結果
if ($response->getStatusCode() == 200) {
file_put_contents("/tmp/success_{$i}", "success");
} else {
file_put_contents("/tmp/fail_{$i}", "fail");
}
exit(0); // 子行程結束
}
}
// 父行程:等待所有子行程結束
foreach ($processes as $pid) {
pcntl_waitpid($pid, $status);
}
// 驗證結果
$successCount = 0;
$failCount = 0;
for ($i = 0; $i < $numRequests; $i++) {
if (file_exists("/tmp/success_{$i}")) {
$successCount++;
unlink("/tmp/success_{$i}");
}
if (file_exists("/tmp/fail_{$i}")) {
$failCount++;
unlink("/tmp/fail_{$i}");
}
}
// 判斷只有一個成功,並且庫存減少 1
expect($successCount)->toBe(1);
expect($failCount)->toBe($numRequests - 1);
$finalStock = DB::table('products')->where('id', $productId)->value('stock');
expect($finalStock)->toBe($initialStock - 1);
});
程式碼說明:
pcntl_fork()
: 建立一個新的行程。父行程和子行程都會繼續執行後面的程式碼。/api/checkout
端點發送 POST 請求,模擬下單。Laravel 提供了內建的並行測試功能,您可以使用 php artisan test --parallel
命令來執行測試。--processes
選項允許您指定要使用的行程數。
範例程式碼:
首先,我們需要建立一個普通的 Pest 測試案例:
<?php
use function Pest\test;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\DB;
// 假設我們的 API 端點是 /api/checkout
$apiEndpoint = '/api/checkout';
test('API 應限制並行購買請求 (使用 Laravel 並行測試)', function () {
$productId = 1;
$initialStock = 10;
// 建立測試資料
DB::table('products')->insert([
'id' => $productId,
'stock' => $initialStock,
]);
// 模擬發送請求
$responses = [];
for ($i = 0; $i < 5; $i++) {
$responses[] = Http::post($apiEndpoint, ['product_id' => $productId, 'quantity' => 1]);
}
$successCount = 0;
$failCount = 0;
foreach ($responses as $response) {
if ($response->getStatusCode() == 200) {
$successCount++;
} else {
$failCount++;
}
}
// 判斷只能有一個成功,並且庫存減少 1
expect($successCount)->toBe(1);
expect($failCount)->toBe(4);
$finalStock = DB::table('products')->where('id', $productId)->value('stock');
expect($finalStock)->toBe($initialStock - 1);
});
然後,在終端機中執行以下命令:
php artisan test --parallel --processes=4
這將會使用 4 個行程來並行執行您的測試。
特性 | pcntl | Laravel 並行測試 |
---|---|---|
易用性 | 較複雜,需要自行管理行程的建立、執行和同步。 | 更簡單,使用 php artisan test --parallel 命令即可輕鬆實現。 |
資料庫處理 | 需要自行處理資料庫隔離,例如使用交易或為每個行程建立單獨的連線。 | 自動為每個行程建立一個測試資料庫,確保測試之間的資料隔離。 |
效能 | 可以更靈活地控制行程的行為,可能在某些特定場景下有更好的效能。 | 針對一般測試場景進行了優化,可以更有效地利用系統資源。 |
適用場景 | 適用於需要精確控制並行行為的特定測試場景, 例如模擬多個使用者同時與 API 互動並驗證其並行處理邏輯。 | 適用於加速一般的測試執行,特別是對於測試套件執行時間較長的專案。 |
伺服器依賴 | 需要伺服器支援 pcntl 擴充套件。 | 對伺服器環境的要求較低 |
總結
php artisan test --parallel --processes=4
是一種更簡單、更高效的方式來並行執行 Laravel 測試,特別是對於加速測試套件的執行。pcntl
(如範例所示)提供了更大的靈活性。