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

24

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

基本上,我启动了一个事务,需要在同一张表上执行SELECTINSERT才能提交。

但是由于负载非常高,每个事务都会锁定该表,在大多数情况下,它非常快速,不会造成任何问题,但是有一个点,锁开始等待越来越多。

我通过调整查询语句来部分解决了这个问题。

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

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

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

基本上,我想能够在单个测试中运行多个调用以确保一切正常。

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

有什么想法吗?


有同样的问题 - XuDing
你无法以常规方式测试并发。你唯一能做的是a) 压力测试和b) 强制执行你认为异常情况会发生的条件(如果可能的话)。基本上你根本不应该测试数据库,你应该确保白皮书说明你的算法是安全的,如果你想要更加确保安全,可以在最高测试级别(应用程序之外)添加特定的压力测试。回答具体问题,你可以始终使用多进程执行并发操作,因此手动分叉或像kirsswallsmith/spork这样的库可以帮助。 - Etki
1个回答

8
短暂的答案是,使用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脚本。这些脚本可以被多次生成以对数据库施加负载。或者制作一个简单的网页与库一起使用,并使用众多的负载测试应用程序之一来测试它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接