Dav*_*tom 9 php phpunit unit-testing qa
对于组成另一个对象作为其实现的一部分的对象,编写单元测试的最佳方法是什么,只有主要对象才会被测试?琐碎的例子:
class myObj {
public function doSomethingWhichIsLogged()
{
// ...
$logger = new logger('/tmp/log.txt');
$logger->info('some message');
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
我知道可以设计对象以便可以注入logger对象依赖项并因此在单元测试中进行模拟,但情况并非总是如此 - 在更复杂的场景中,您需要组合其他对象或调用静态方法.
由于我们不想测试logger对象,只有myObj,我们如何进行?我们用测试脚本创建一个stubbed"double"吗?就像是:
class logger
{
public function __construct($filepath) {}
public function info($message) {}
}
class TestMyObj extends PHPUnit_Framework_TestCase
{
// ...
}
Run Code Online (Sandbox Code Playgroud)
这对于小对象来说似乎是可行的,但对于更复杂的API而言,这将是一个痛苦,其中SUT依赖于返回值.另外,如果你想测试对依赖项对象的调用,你可以使用模拟对象吗?有没有办法模拟由SUT实例化的对象而不是传入?
我已经阅读了关于模拟的手册页,但它似乎并没有涵盖这种依赖是由组合而不是聚合的情况.你怎么做呢?
跟随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
}
}
Run Code Online (Sandbox Code Playgroud)
有关模拟对象的更多信息,请参见手册.
正如您似乎已经意识到的那样,具体类依赖关系使测试变得困难(或完全不可能)。您需要解耦这种依赖关系。一个简单的更改不会破坏现有的 API,即默认为当前行为,但提供一个钩子来覆盖它。有多种方法可以实现这一点。
有些语言有可以将模拟类注入代码的工具,但我不知道 PHP 有这样的东西。在大多数情况下,无论如何重构代码可能会更好。