在PHPUnit中测试具有依赖项的对象

时间:2009-02-13 09:12:44

标签: php unit-testing phpunit qa

对于组成另一个对象作为其实现的一部分的对象,编写单元测试的最佳方法是什么,只有主要对象才会被测试?琐碎的例子:

class myObj { 
    public function doSomethingWhichIsLogged()
    {
        // ...
        $logger = new logger('/tmp/log.txt');
        $logger->info('some message');
        // ...
    }
}

我知道可以设计对象以便可以注入logger对象依赖项并因此在单元测试中进行模拟,但情况并非总是如此 - 在更复杂的场景中,您确实需要编写其他对象或进行调用静态方法。

由于我们不想测试logger对象,只有myObj,我们如何进行?我们用测试脚本创建一个stubbed“double”吗?类似的东西:

class logger
{
    public function __construct($filepath) {}
    public function info($message) {}
}

class TestMyObj extends PHPUnit_Framework_TestCase 
{
    // ...
}

对于小对象来说这似乎是可行的,但对于更复杂的API而言,这将是一个痛苦,其中SUT依赖于返回值。另外,如果你想测试对依赖项对象的调用,你可以使用模拟对象吗?有没有办法模拟由SUT实例化的对象而不是传入?

我已经阅读了关于模拟的手册页,但它似乎并没有涵盖这种依赖是组合而不是聚合的情况。你是怎么做到的?

4 个答案:

答案 0 :(得分: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
    }
}

有关模拟对象的更多信息,请访问in the manual

答案 1 :(得分:4)

正如您似乎已经意识到的那样,Concrete Class Dependencies使得测试变得困难(或完全不可能)。您需要解除该依赖关系。一个不破坏现有API的简单更改是默认为当前行为,但提供一个钩子来覆盖它。有很多方法可以实现。

有些语言的工具可以将模拟类注入到代码中,但我不知道PHP的这类内容。在大多数情况下,无论如何,最好还是重构代码。

答案 2 :(得分:0)

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

你应该使用单件模式或工厂作为记录器,如果还不太晚:

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

如果您无法更改代码,则可以使用重载{-3}}的全包类

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

答案 3 :(得分:0)

实际上,构建PHPUnit的同一个人发布了PHP类重载的合理新扩展。它允许您在无法重构代码的情况下覆盖新运算符,遗憾的是,在Windows上安装并不是那么简单。

网址为http://github.com/johannes/php-test-helpers/blob/master/