【问题标题】:Testing a method which completely uses another objects methods测试完全使用另一个对象方法的方法
【发布时间】:2013-02-22 21:55:49
【问题描述】:

我正在重构一个类,同时为它编写单元测试。 有一种情况是我的一个方法完全调用了另一个对象的方法,这些方法注入到我正在测试的这个类中。

所以我必须模拟我注入类的对象。

现在,问题是,是否值得为这个特定方法编写单元测试? 编写单元测试似乎很奇怪,它调用其他对象的方法而该对象本身必须被模拟,那我为什么要测试这个方法呢?

测试的目的不是检查方法的功能是否按预期工作吗? 如果是,那么当我嘲笑它所拥有的一切并且该特定方法没有任何东西可以测试时,我为什么要测试?

我真的很困惑!

我坚持的方法是这个(用于自定义会话处理):

public function write($sessionId, $sessionData)
{
    $sth = $this->databaseConnection->prepare("INSERT INTO `{$this->DBTableName}` (`session_id`,`session_name`,`session_data`) VALUES(:session_id, :session_name, :session_data) ON DUPLICATE KEY UPDATE `session_data`=:session_data;");
    $sth->bindValue(':session_id', $sessionId);
    $sth->bindValue(':session_name', $this->sessionName);
    $sth->bindValue(':session_data', $sessionData);

    return $sth->execute();
}

这里也是这段代码的链接:http://pastebin.com/1FBeU6mb

顺便说一句,我刚开始为我的课程编写测试,我是这个测试领域的初学者,没有经验。

提前致谢。

【问题讨论】:

    标签: php unit-testing testing phpunit


    【解决方案1】:

    我对 php 不是很熟悉,但看起来你只是在构建和执行数据库查询,对吧?

    除非在这个方法和数据库之间有其他我看不到的层,否则这里真的没有什么值得嘲笑的。所以你是对的,在这里嘲笑并没有给你很多价值。而且从某种意义上说,测试这种方法的价值是有限的,因为您实际上只是在测试数据库层,通常我们可以假设它已经正确且稳定,因此不需要测试。

    一般来说,模拟的价值在于它允许您通过假设其他方法正在做什么来简化测试,并且允许您不必间接测试那些其他方法。

    在选择测试 write 方法时,事实证明您正在测试的是您有正确的步骤来返回正确的结果。而已。我不熟悉 php 模拟框架,但我知道对于其他语言的框架,您可以设置所谓的期望,它可以让您指定将在具有某些参数的某个对象上调用某个方法。您甚至可以经常指定它们的执行顺序。这里的要点是您正在测试您的对象正在发送的 outgoing 消息,而不是这些消息的返回值。

    由您决定这是否有价值与此测试所需的维护。

    【讨论】:

    • 感谢您抽出时间回答
    【解决方案2】:

    您正在测试是否将正确的参数传递给您准备好的语句。此外,您应该测试write 方法是否返回准备好的语句结果。

    如果您不对此进行测试,您的应用程序可能会以多种方式中断。

    • 重命名或删除方法参数($sessionId$sessionData
    • 重命名您的属性$this->sessionName
    • 删除bindValue 调用之一。
    • 绑定别名命名错误。它们应该与您的查询匹配。
    • 返回除 execute() 的结果之外的其他内容。

    等等。等等

    所以是的,测试这个是个好习惯。

    【讨论】:

    • (Hossein 让我在 twitter 上对此发表评论)我认为 Bram 涵盖了我会提出的所有内容,除了关于这是否最好作为单元测试完成的决定(这意味着你将拥有模拟所有依赖项)或在集成测试中(这意味着您将需要一个专用的测试数据库并在测试后进行清理)我不是集成测试的忠实粉丝(您最终会复制很多单元测试代码)所以我的首选是编写广泛的单元测试,然后使用 Behat 等工具对应用程序本身进行验收测试。
    • @GrumpyCanuck 谢谢,是的,Baram Gerriten 的 commnet 帮助了我很多。
    【解决方案3】:

    假设您的示例描述了一个 SessionHandler 类,它看起来类似于:

    class SessionHandler
    {
        public function __construct($sessionTableName, $sessionName, \PDO $databaseConnection)
        { 
            $this->DBTableName = $sessionTableName;
            $this->sessionName = $sessionName;
            $this->databaseConnection = $databaseConnection;
        }
    
        // among others, your method write($sessionId, $sessionData) follows
    }
    

    这可以覆盖write()的方法:

    public function testWriteInsertsOrUpdatesSessionData()
    {
        /**
         * initialize a few explaining variables which we can refer to 
         * later when arranging test doubles and eventually act
         */
        $sessionTableName = 'sessions';
        $sessionName = 'foobarbaz';
    
        $sessionId = 'foo';
        $sessionData = serialize([
            'bar' => 'baz',
        ]);
    
        $executed = true;
    
        /**
         * create a test double for the statement that we expect to be returned 
         * from `PDO::prepare()`
         */
        $statement = $this->createMock(\PDOStatement::class);
    
        /**
         * set up expectations towards which methods should be invoked 
         * on the statement, specifying their order
         */
        $statement
            ->expects($this->at(0))
            ->method('bindValue')
            ->with(
                $this->identicalTo(':session_id'),
                $this->identicalTo(sessionId)
            );
    
        $statement
            ->expects($this->at(1))
            ->method('bindValue')
            ->with(
                $this->identicalTo(':session_name'),
                $this->identicalTo($sessionName)
            );
    
        $statement
            ->expects($this->at(2))
            ->method('bindValue')
            ->with(
                $this->identicalTo(':session_data'),
                $this->identicalTo(sessionData)
            );
    
        $statement
            ->expects($this->at(3))
            ->method('execute')
            ->willReturn($executed);
    
        /**
         * create a test double for the database connection we inject
         * into SessionHandler during construction
         */
        $databaseConnection = $this->createMock(\PDO::class);
    
        $databaseConnection
            ->expects($this->once())
            ->method('prepare')
            ->with($this->identicalTo(sprintf(
                'INSERT INTO `%s` (`session_id`,`session_name`,`session_data`) VALUES(:session_id, :session_name, :session_data) ON DUPLICATE KEY UPDATE `session_data`=:session_data;',
                $sessionTableName
            )))
            ->willReturn($statement);
    
        $sessionHandler = new SessionHandler(
            $sessionTableName,
            $sessionName,
            $databaseConnection
        );
    
        $result = $sessionHandler->write(
            $sessionId,
            $sessionData
        );
    
        $this->assertSame($executed, $result);
    }
    

    参考见:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-21
      • 1970-01-01
      • 2020-01-11
      相关资源
      最近更新 更多