PropelORM,Symfony 2和单元测试

时间:2012-03-12 09:40:57

标签: php unit-testing symfony phpunit propel

我习惯于写这样的习惯:

$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();

1 个答案:

答案 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());
    }
}

我给出了一个示例,其中调用了真实的查询代码,并且模拟了查询代码。

因此,这使您能够注入模拟,而无需将每个依赖项注入要进行单元测试的类中。

编写上述代码的方法有很多种。将其用作概念证明并根据您的需要进行调整。