如何使用Pest测试Laravel应用中的竞争条件(支持不同请求体的并发请求)
如何使用Pest测试Laravel应用中的竞争条件(支持不同请求体的并发请求)
嘿,我完全懂你的困扰!想用Pest测试Laravel应用的竞争条件,但自带的方法要么是挨个发请求(完全没并发效果),要么是stress()重复发同一个请求体,根本满足不了你测不同请求并发的需求对吧?别慌,我给你分享两个实用的方案,能搞定这个问题~
方案一:用Guzzle并发请求池发送不同请求体
默认的Pest/Laravel测试客户端是在同一个进程里模拟请求的,没法实现真实的并发。所以我们可以用Guzzle的并发请求池,直接向运行中的Laravel服务器发送真实的HTTP请求,每个请求带不同的payload。
具体实现代码
use GuzzleHttp\Client; use GuzzleHttp\Pool; use GuzzleHttp\Psr7\Request; test('测试不同请求体的并发竞争条件', function () { // 先确保你的Laravel服务器在运行,比如用`php artisan serve`启动在8000端口 $serverUrl = 'http://localhost:8000'; // 准备你需要测试的不同请求体 $payload1 = ['key' => 'value1', 'data' => 'test1']; $payload2 = ['key' => 'value2', 'data' => 'test2']; $client = new Client(); // 构建多个不同的POST请求 $requests = [ new Request( 'POST', $serverUrl . action('YourController@yourAction'), ['Content-Type' => 'application/json'], json_encode($payload1) ), new Request( 'POST', $serverUrl . action('YourController@yourAction'), ['Content-Type' => 'application/json'], json_encode($payload2) ), // 还可以继续添加更多不同的请求 ]; // 创建并发请求池,设置并发数等于请求数量 $pool = new Pool($client, $requests, [ 'concurrency' => count($requests), 'fulfilled' => function ($response, $index) { // 验证每个请求都成功返回 expect($response->getStatusCode())->toBe(200); }, 'rejected' => function ($reason, $index) { // 如果请求失败,抛出异常终止测试 throw new Exception("第 {$index} 个请求失败: {$reason->getMessage()}"); }, ]); // 启动所有请求并等待全部完成 $promise = $pool->promise(); $promise->wait(); // 这里添加核心断言,检查竞争条件是否触发了预期问题 // 比如验证数据库中的数据是否符合预期(有没有重复、数据不一致等) $recordCount = YourModel::count(); expect($recordCount)->toBe(2); // 示例断言,根据你的业务调整 });
方案二:用PHP多进程(pcntl扩展)实现并发
如果你需要更底层的并发控制,可以用PHP的pcntl扩展创建子进程,每个子进程单独发送一个带不同payload的请求。
具体实现代码
use GuzzleHttp\Client; test('用多进程测试不同请求体的竞争条件', function () { $serverUrl = 'http://localhost:8000'; // 准备不同的请求体数组 $payloads = [ ['key' => 'value1', 'data' => 'test1'], ['key' => 'value2', 'data' => 'test2'], ]; $childPids = []; foreach ($payloads as $payload) { $pid = pcntl_fork(); if ($pid === -1) { throw new Exception('无法创建子进程,请确保已启用pcntl扩展'); } elseif ($pid === 0) { // 子进程逻辑:发送请求 $client = new Client(); $response = $client->post( $serverUrl . action('YourController@yourAction'), ['json' => $payload] ); // 子进程退出,返回状态码 exit($response->getStatusCode()); } else { // 父进程记录子进程ID $childPids[] = $pid; } } // 等待所有子进程完成,并验证每个请求的状态 foreach ($childPids as $pid) { pcntl_waitpid($pid, $status); expect(pcntl_wexitstatus($status))->toBe(200); } // 同样添加竞争条件的核心断言 // ... });
额外小贴士:自动启动临时测试服务器
如果你不想每次测试前手动启动Laravel服务器,可以用Laravel自带的Process类(基于Symfony Process)在测试中自动启动和关闭临时服务器:
use Symfony\Component\Process\Process; use GuzzleHttp\Client; use GuzzleHttp\Pool; use GuzzleHttp\Psr7\Request; test('自动启动服务器测试竞争条件', function () { // 启动临时服务器,用8001端口避免和本地服务冲突 $process = new Process(['php', 'artisan', 'serve', '--port', '8001']); $process->start(); // 给服务器预留2秒启动时间 sleep(2); try { $serverUrl = 'http://localhost:8001'; // 这里放前面的并发请求代码,把地址改成上面的临时服务器地址 // ... } finally { // 不管测试成功还是失败,都关闭临时服务器 $process->stop(); } });
注意事项
- 一定要用测试数据库,避免竞争条件测试影响生产或开发数据;
- 竞争条件测试可能不稳定,因为并发时机不可控,可能需要多次运行才能复现问题;
- 如果使用
pcntl扩展,要确保你的PHP环境已经启用了这个扩展(通常在CLI环境下可用); - 临时服务器的启动时间可能需要调整,如果服务器启动慢,可以适当延长
sleep的时间。
备注:内容来源于stack exchange,提问作者mankowitz
相关产品推荐
相关产品推荐

