我习惯于写这样的习惯:
$results = SomeModelQuery::create()->filterByFoo('bar')->find();
然而,这不适用于单元测试,因为我无法注入模拟对象,即我无法影响返回的数据。我想使用夹具数据,但我不能。
注入一个对象似乎也不是很好:
class Foo
{
public __construct($someModelQuery)
{
$this->someModelQuery = $someMOdelQuery;
}
public function doSthing()
{
$results = $this->someModelQuery->filterByFoo('bar')->find();
}
}
DI感觉太可怕了。我有几十个查询对象来模拟和抛出。通过构造函数设置是丑陋和痛苦的。设置使用方法是错误的,因为在调用时可能会忘记它。并且总是为每个单独的lib和动作手动创建这些查询对象感到痛苦。
我如何优雅地使用PropelORM查询类进行DI?我不想调用像这样的方法:
$oneQuery = OneQuery::create();
$anotherQuery = AnotherQuery::create();
// ... 10 more ...
$foo = new Foo($oneQuery, $anotherQuery, ...);
$foo->callSomeFunctionThatNeedsThose();
答案 0 :(得分:3)
在我看来(和Martin Folowers's)在静态调用所有内容和使用依赖注入之间有一个步骤,它可能就是你要找的东西。
如果我无法完成DI(例如Zend Framework MVC),我将使用服务定位器。服务层将成为您所有类从中获取依赖关系的地方。可以将它视为类依赖关系的一层深层抽象。使用服务定位器有很多好处,但在这种情况下我会关注可测试性。
让我们进入一些代码,这里是模型查询类
class SomeModelQuery
{
public function __call($method, $params)
{
if ($method == 'find') {
return 'Real Data';
}
return $this;
}
}
除非方法“找到”,否则它只会自行返回。叫做。然后将返回硬编码字符串" Real Data"。
现在我们的服务定位器:
class ServiceLocator
{
protected static $instance;
protected $someModelQuery;
public static function resetInstance()
{
static::$instance = null;
}
public static function instance()
{
if (self::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
public function getSomeModelQuery()
{
if ($this->someModelQuery === null) {
$this->someModelQuery = new SomeModelQuery();
}
return $this->someModelQuery;
}
public function setSomeModelQuery($someModelQuery)
{
$this->someModelQuery = $someModelQuery;
}
}
这有两件事。提供全局范围方法实例,以便您始终可以获取它。同时允许它重置。然后为模型查询对象提供get和set方法。如果尚未设置,则延迟加载。
现在执行实际工作的代码:
class Foo
{
public function doSomething()
{
return ServiceLocator::instance()
->getSomeModelQuery()->filterByFoo('bar')->find();
}
}
Foo调用服务定位器,然后从中获取查询对象的实例,并在该查询对象上执行所需的调用。
所以现在我们需要为所有这些编写一些单元测试。这是:
class FooTest extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
ServiceLocator::resetInstance();
}
public function testNoMocking()
{
$foo = new Foo();
$this->assertEquals('Real Data', $foo->doSomething());
}
public function testWithMock()
{
// Create our mock with a random value
$rand = mt_rand();
$mock = $this->getMock('SomeModelQuery');
$mock->expects($this->any())
->method('__call')
->will($this->onConsecutiveCalls($mock, $rand));
// Place the mock in the service locator
ServiceLocator::instance()->setSomeModelQuery($mock);
// Do we get our random value back?
$foo = new Foo();
$this->assertEquals($rand, $foo->doSomething());
}
}
我给出了一个示例,其中调用了真实的查询代码,并且模拟了查询代码。
因此,这使您能够注入模拟,而无需将每个依赖项注入要进行单元测试的类中。
编写上述代码的方法有很多种。将其用作概念证明并根据您的需要进行调整。