简短的回答是,没有好的方法可以使用PHPUnit在实际数据库上测试并发读/写。它根本不是这项工作的正确工具。
但是,测试此内容的良好解决方案有几个方面。首先,可以(并且应该)编写代码来处理每个可能的问题。像PostgreSQL这样的数据库系统会在锁定和事务问题时立即失败。为了优雅地处理它,我使用看起来像这样的代码(伪代码,也用于回答另一个问题):
begin transaction
while not successful and count < 5
try
execute sql
commit
except
if error code is '40P01' or '55P03'
# Deadlock or lock not available
sleep a random time (200 ms to 1 sec) * number of retries
else if error code is '40001' or '25P02'
# "In failed sql transaction" or serialized transaction failure
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;
}
});
// First time fails, second time succeeds
$this->assertEquals(2, $iterationCount, 'Expected 2 iterations of runInTransaction');
编写一套完整的测试,这些测试不连接到数据库,但确认逻辑。
集成测试
正如你所发现的,PHPUnit 根本不是执行负载测试的正确工具。它不适合比顺序单元和集成测试更复杂的内容。您可以同时运行 PHPUnit 的多个实例,以增加数据库的负载。但是,我发现这超出了它的本意,而且它不能帮助您监视数据库是否存在问题。因此,我认为没有办法绕过您希望避免的更高级别的测试。
但是,可以在不运行完整应用程序的情况下测试您的库。我将创建最简单的应用程序,仅用于测试它。它可以有一个或多个连接到数据库的 CLI 脚本。可以多次生成这些脚本以将负载放在数据库上。或者使用该库创建一个简单的网页,并使用许多负载测试应用程序中的任何一个来测试它。