如何在phpunit中为进行mysql调用的类编写单元测试?

时间:2016-11-30 10:51:44

标签: phpunit

我有一个示例类,它执行mysql操作,如下所示。

<?php
class ProjectHandler{
    public function getProjectInformation($projectId){
        $prMysql = new prMysql;
        /* Make connection to database */
        $connection = $prMysql->open_store_database();
        $sql = sprintf("SELECT product_id, projectName FROM test_projects
                WHERE projectId = %d", $platform_id);
        $test_result = mysql_query($sql, $connection) or die();
        $num_rows = mysql_num_rows($test_result);
        if ($num_rows > 0) {
            $test_row = mysql_fetch_assoc($test_result);
        }
        return $test_row;
    }
}
?>

其中prMysql是为mysql操作编写的包装类。是否可以在场景中模拟mysql调用?

1 个答案:

答案 0 :(得分:3)

是的,你可以做到

您可以采取两种方法。鉴于您提供的代码示例,两者都有其缺点。

我将假设mysql_query()是一个拼写错误,你的意思是mysqli_query()。有关您不应使用mysql_*功能的原因以及可以执行的操作的详情,请参阅How can I prevent SQL injection in PHP?

使用依赖注入

鉴于你的陈述

  

其中prMysql是为mysql操作编写的包装类

出于本部分答案的目的,我将假设mysql_query($sql, $connection)之类的调用实际上写为$prMysql->query($sql),从而包含实际调用。我也将假设num_results()部分也在该方法中完成。如果您选择此重构解决方案,则需要使用prMysql类进行查询以及连接。

dependency injection背后的想法是你没有在使用它的类中实例化依赖项(prMysql),但在它之外。这在单元测试中很有用,因为你可以给正在测试的类提供假的或“模拟”对象。可以构造此模拟对象,使其始终将已知值返回到已知输入。

这种方法的缺点是你需要重构ProjectHandler类以将其作为依赖项传递。类似下面的内容应该足够了,但您可能需要在开放连接调用的位置进行操作:

class ProjectHandler
{
    /** @var prMysql */
    private $database;

    /**
     * @param prMysql $database
     */
    public function __construct(prMysql $database)
    {
        $this->database = $database;
    }

    /**
     * @param mixed $projectId
     * @return array
     */
    public function getProjectInformation($projectId)
    {
        $prMysql = $this->database;
        $sql = sprintf("SELECT product_id, projectName FROM test_projects
            WHERE projectId = %d", $platform_id);
        $test_row = $pyMysql->query($sql);
        return $test_row;
    }
}

这意味着您可以在测试时轻松提供模拟prMysql对象,而无需更改被测系统的任何代码。在您的测试方法中,您可以使用以下内容:

public function testGetProjectInformation()
{
    // Here, we create a mock prMysql object so we don't use the original
    $prMysql = $this->getMockBuilder(prMysql::class)
        ->disableOriginalConstructor()
        ->getMock();

    /* Here, we say that we expect a call to mysql_query with a given query,
     * and when we do, return a certain result.
     * You will also need to mock other methods as required */
    $expectedQuery = "SELECT product_id, projectName FROM test_projects
            WHERE projectId = 1";
    $returnValue = [['product_id' => 1, 'projectName' => 'test Name']];
    $prMysql->expects($this->once())
        ->method('query')
        ->with($this->equalTo($expectedQuery))
        ->willReturn($returnValue);

    // Here we call the method and do some checks on it
    $object = new ProjectHandler($prMysql);
    $result = $object->getProjectInformation(1);
    $this->assertSame($returnValue, $result);
}

现在,请记住,这只是您需要做的草图。您需要自己填写详细信息。有相当数量的重构要做,但最终还是值得的。

设置模拟数据库

另一种方法是设置一个仅用于测试的数据库,并直接连接到该数据库。

phpunit手册中有关于此的

There is a whole chapter,但对于每个测试,它归结为:

  • 将数据库设置为已知状态
  • 运行测试
  • 断言某些表处于给定状态
  • 拆除连接

在您的情况下,这样做的好处是您必须更改一些代码(如果有的话)。这样做的缺点是你的测试速度非常慢,并且你已经失去了依赖注入等所带来的抽象。