【问题标题】:How to write unit tests in phpunit for classes which make mysql calls?如何在 phpunit 中为进行 mysql 调用的类编写单元测试?
【发布时间】:2017-04-14 15:53:16
【问题描述】:

我有一个执行 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 调用?

【问题讨论】:

    标签: phpunit


    【解决方案1】:

    是的,你可以做到的

    您可以采取两种方法来解决这个问题。鉴于您提供的代码示例,两者都有其缺点。

    我假设mysql_query() 是一个错字,而您的意思是mysqli_query()。请参阅How can I prevent SQL injection in PHP?,了解有关为什么不应该使用mysql_* 函数以及可以做些什么的更多信息。

    使用依赖注入

    鉴于你的陈述

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

    出于这部分答案的目的,我将假设诸如mysql_query($sql, $connection) 之类的调用实际上写为$prMysql-&gt;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);
    }
    

    现在,请记住,这只是您需要做什么的粗略草图。您需要自己填写详细信息。有相当多的重构工作要做​​,但最终还是值得的。

    设置模拟数据库

    另一种选择是建立一个仅用于测试的数据库,然后直接连接到该数据库。

    There is a whole chapter 在 phpunit 手册中对此进行了说明,但归结为,对于每个测试:

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

    在您的情况下,这样做的好处是您只需对代码进行少量更改(如果有的话)。这样做的缺点是您的测试会非常慢,并且您已经失去了依赖注入等所提供的抽象。

    【讨论】:

    • 感谢您的简短解释。另一个快速的问题,是否可以在不更改源代码的情况下编写单元测试?
    • @Siva Ninala 是的,请参阅我的答案的第二部分,标题为“设置模拟数据库”。这会导致你的测试变慢,而且你不会有任何抽象。
    猜你喜欢
    • 2013-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-22
    • 2019-05-05
    • 2012-03-26
    • 2011-06-06
    • 2010-11-16
    相关资源
    最近更新 更多