使用PHPUnit测试具有依赖关系的对象

9
对于将另一个对象作为其实现的一部分组合的对象,最好的方法是如何编写单元测试,以便只测试主要对象? 简单的例子:
class myObj { 
    public function doSomethingWhichIsLogged()
    {
        // ...
        $logger = new logger('/tmp/log.txt');
        $logger->info('some message');
        // ...
    }
}

我知道可以设计对象以便注入记录器对象依赖项,从而在单元测试中进行模拟,但这并不总是适用于更复杂的情况,您可能需要组合其他对象或调用静态方法。
由于我们不想测试记录器对象,仅测试myObj,我们应该如何操作? 我们是否需要在测试脚本中创建一个存根“double”,例如:
class logger
{
    public function __construct($filepath) {}
    public function info($message) {}
}

class TestMyObj extends PHPUnit_Framework_TestCase 
{
    // ...
}

这对于小型对象似乎是可行的,但对于更复杂的API而言,如果SUT依赖于返回值,则会很麻烦。此外,如果您想像使用模拟对象一样测试对依赖对象的调用,该怎么办?有没有一种方法可以模拟由SUT实例化而不是传递的对象?
我已经阅读了关于模拟的手册,但它似乎没有涵盖这种组合而不是聚合依赖的情况。你如何做到这一点?

你看过PHPUnit吗?自从3.4版本引入了测试依赖和夹具重用,它变得更加强大了。http://sebastian-bergmann.de/archives/848-Fixture-Reuse-in-PHPUnit-3.4.html 顺祝商祺,马库斯 - user221212
4个回答

6

根据 troelskn 的建议,这里提供了一个基本示例,展示您应该做些什么。

<?php

class MyObj
{
    /**
     * @var LoggerInterface
     */
    protected $_logger;

    public function doSomethingWhichIsLogged()
    {
        // ...
        $this->getLogger()->info('some message');
        // ...
    }

    public function setLogger(LoggerInterface $logger)
    {
        $this->_logger = $logger;
    }

    public function getLogger()
    {
        return $this->_logger;
    }
}


class MyObjText extends PHPUnit_Framework_TestCase
{
    /**
     * @var MyObj
     */
    protected $_myObj;

    public function setUp()
    {
        $this->_myObj = new MyObj;
    }

    public function testDoSomethingWhichIsLogged()
    {
        $mockedMethods = array('info');
        $mock = $this->getMock('LoggerInterface', $mockedMethods);
        $mock->expects($this->any())
             ->method('info')
             ->will($this->returnValue(null));

        $this->_myObj->setLogger($mock);

        // do your testing
    }
}

关于模拟对象的更多信息可以在 手册 中找到。


4

正如您已经意识到的那样,具体类依赖关系会使测试变得困难(或者根本不可能)。您需要解耦这种依赖关系。一个简单的更改,不会破坏现有的API,就是默认使用当前行为,但提供一个钩子来覆盖它。有许多实现方式。

一些语言有工具可以将模拟类注入代码中,但我不知道PHP有类似的工具。在大多数情况下,您可能最好还是重构代码。


你好,看起来你的意思是任何形式的聚合都不好,因为它们都是具体的依赖关系,这是一种硬耦合,因此很难测试。这样说对吗?我想我同意,但这是否会导致一个庞大的引导脚本,将所有依赖对象注入到需要它们的地方呢? - DavidWinterbottom
1
@troelskn,只是出于好奇,您能否举个例子,介绍一下您所说的那些语言的工具? - Ionuț G. Stan
@david 对于第一个问题是肯定的,对于第二个问题也算是肯定的,只是“繁重”这个词比较主观。依赖注入容器是减轻负担的一种方式。 - troelskn
1
@ionut 我在思考使用自定义类加载器的 http://code.google.com/p/powermock/。 - troelskn

0

实际上,PHPUnit 的开发者发布了一个相当新的 PHP 类重载扩展。它允许您在无法重构代码的情况下覆盖 new 操作符,但不幸的是,在 Windows 上安装并不那么简单。

链接地址为 http://github.com/johannes/php-test-helpers/blob/master/


0

看起来我误解了问题,让我再试一次:

如果还没有太晚,你应该使用单例模式或工厂模式来记录器:

class LoggerStub extends Logger {
    public function info() {}
}
Logger::setInstance(new LoggerStub());
...
$logger = Logger::getInstance();

如果您无法更改代码,可以使用一个重载__call()的万能类。

class GenericStub {
    public function __call($functionName, $arguments) {}
}

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