短暂的答案是,使用PHPUnit测试实际数据库上的并发读写没有好的方法。它简单地不是正确的工具。
但是,测试这个问题的好解决方案有几个方面。首先,代码可以(而且应该)编写以处理每个可能的问题。像PostgreSQL这样的数据库系统将立即在锁定和事务问题上失败。为了优雅地处理它,我使用类似于以下伪代码的代码(也用于回答
另一个问题):
begin transaction
while not successful and count < 5
try
execute sql
commit
except
if error code is '40P01' or '55P03'
sleep a random time (200 ms to 1 sec) * number of retries
else if error code is '40001' or '25P02'
rollback
sleep a random time (200 ms to 1 sec) * number of retries
begin transaction
else if error message is 'There is no active transaction'
sleep a random time (200 ms to 1 sec) * number of retries
begin transaction
increment count
然后创建两组测试:一组用于确认代码是否正确处理情况(即单元测试)。另一组测试是针对环境的(即集成/功能测试)。
单元测试
我发现使用连接到数据库的 PHPUnit 测试来复制这种情况很困难,而使用真实数据库不适合真正的单元测试。相反,创建 PDO 存根和单元测试,抛出每种数据库异常。这将确认代码按预期工作,但
不会在任何真实数据库上测试并发性。请记住,单元测试仅用于确认
您的代码是否正确编写,而不是用于测试第三方软件。
$iterationCount = 0;
$db->runInTransaction(function() use (&$iterationCount) {
$iterationCount++;
if ($iterationCount === 1) {
$exception = new PDOExceptionStub('Deadlock');
$exception->setCode('40P01');
throw $exception;
}
});
$this->assertEquals(2, $iterationCount, 'Expected 2 iterations of runInTransaction');
编写一套完整的测试套件,不连接到数据库,但确认逻辑。
集成测试
正如您所发现的那样,PHPUnit并不是执行负载测试的正确工具。它不适用于比顺序单元和集成测试更复杂的任何内容。您可以同时运行多个PHPUnit实例以对数据库施加更多负载。然而,我认为这超出了它的本意,并且它不能帮助您监视数据库是否存在问题。因此,我看不到您想要避免更高级别测试的方法。
但是,您的库可以在不运行完整应用程序的情况下进行测试。我会创建一个仅供测试的最简单的应用程序。它可以有一个或多个连接到数据库的CLI脚本。这些脚本可以被多次生成以对数据库施加负载。或者制作一个简单的网页与库一起使用,并使用众多的负载测试应用程序之一来测试它。