题
有关这构成另一个对象作为其实现的一部分的对象,什么是写单元测试,以便只有原则对象被测试过的最好方法是什么?简单的例子:
class myObj {
public function doSomethingWhichIsLogged()
{
// ...
$logger = new logger('/tmp/log.txt');
$logger->info('some message');
// ...
}
}
我知道,物体可以被设计成记录器对象的依赖将被注入,因此在单元测试嘲笑,但事实并非总是如此 - 在更复杂的情况下,你需要编写其他物体或拨打电话到静态方法。
由于我们不想测试记录器对象,只有MyObj中,我们该如何进行呢?难道我们创建了一个存根“双规”与测试脚本?是这样的:
class logger
{
public function __construct($filepath) {}
public function info($message) {}
}
class TestMyObj extends PHPUnit_Framework_TestCase
{
// ...
}
这似乎对于小物体可行的,但是将是更复杂的API,其中SUT依赖于返回值的疼痛。另外,如果你想测试什么,在相同的依赖对象的调用是可以使用模仿对象?是否有嘲笑其由SUT实例化,而不是在被传递?对象的方法
我读过嘲笑男人页面,但它似乎并没有涵盖这一情况的依赖性由而非聚集。你怎么办呢?
解决方案
你似乎知道已经,混凝土类依赖性使得测试硬(或完全不可能)。你需要去耦这种依赖性。一个简单的变化,不打破现有的API,是默认为当前的行为,而是提供了一个钩来覆盖它。有许多的,这可以被实现的方式。
某些语言有可注入mock类为代码的工具,但我不知道这样的PHP什么。在大多数情况下,你可能会离开反正重构你的代码更好。
其他提示
下面的 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
}
}
约模拟对象的更多信息可以发现手动一个在>
看起来像我误解了这个问题,让我再试一次:
您应该使用Singleton模式或工厂的记录,如果它不算太晚的话:
class LoggerStub extends Logger {
public function info() {}
}
Logger::setInstance(new LoggerStub());
...
$logger = Logger::getInstance();
如果你不能改变的代码,你可以用它了超载的 __()调用
class GenericStub {
public function __call($functionName, $arguments) {}
}
有实际上是通过建立PHPUnit的同一人发布的PHP类重载一个合理的新的扩展。它可以让你在重载情况下,新的运营商,你不能重构代码,遗憾的是它并不那么简单安装在Windows上。
不隶属于 StackOverflow