如何用PHPUnit对并发读写进行单元测试?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (1)
  • 关注 (0)
  • 查看 (22)

我最近遇到了一个实时应用程序的问题。我意识到我有越来越多的并发异常和数据库锁定。

基本上我开始一个事务,它需要一个SELECT和一个INSERT在同一个表上提交。

但是由于负载非常重,每个事务都会锁定表,在大多数情况下,它非常快速,不会引起任何问题,但是有一点可以使锁开始越来越多地等待。

通过调整查询,我能够稍微解决这个问题。

虽然,现在,我想用PHPUnit编写一些测试来验证我的修复并避免任何回归。

我无法找到关于如何做到这一点的任何材料。

由于PHP不是多线程的,我不知道如何在单个测试中运行并发查询来验证。

基本上,我希望能够在单个测试中运行多个呼叫,以确保一切正常。

我知道我可以尝试通过直接查询http服务器并加载整个应用程序来进行一些高级别的测试,但由于我的问题来自独立库,我宁愿对其自身进行测试。

有任何想法吗?

提问于
用户回答回答于

简短的回答是,使用PHPUnit测试实际数据库的并发读写操作没有好方法。这根本就不是这项工作的正确工具。

但是测试这个方法的好方法有几个方面。首先可以编写代码来处理每种可能的情况。像Postgres这样的数据库系统会立即出现锁和事务问题。为了处理它,我使用的代码看起来像这样:

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脚本。这些脚本可以多次产生,以将负载加载到数据库上。如果有帮助,这也可以让你有一个应用程序与监控数据库本身的附加代码。

扫码关注云+社区