测试和模拟嵌套容器

时间:2015-11-16 17:44:53

标签: php unit-testing phpunit service-locator

我最近接手了一个PHP项目,其中包含很少甚至没有测试。这个项目相当大,课程大多都很小,但有一个大问题。

有一个服务定位器巧妙地隐藏在受保护或私有变量DI中,因此程序员认为他们正在做正确的事情,它充当单身并且几乎传递给每个单独的类。一个类,它反过来使用它来检索依赖项。

上周四我创建了一个由3人组成的新团队,他们的新职责是专注于测试及其自动化,今天终于有一个人带着我担心的问题来找我。一个问题,我不知道答案。

  大卫,我怎么想嘲笑方法的结果呢   深藏在DI

重写模块以遵循DI是不可接受的,我们既没有预算也没有时间去做。

可以像这样调用do()方法吗?

class Baz extends AbstractBaz
{
    public function foo()
    {
        $userProcess = $this->DI->Foo->Bar->FooBar->BarFoo->getUserBar();
        $users = $userProcess->do();

        // work with the $users variable
    }
}

定位器本身非常庞大,你可以通过越来越深入地调用数百种方法。

有没有办法快速模拟Foo->Bar->FooBar->BarFoo->getUserBar结果?这些变量可通过魔术__get方法获得,并由@property注释提示。

使用PHPUnit,有这样的东西会很好:

$locator = $this
    ->getMockBuilder('\App\DI\Abstracted\DI')
    ->setMethods(['Foo->Bar->FooBar->BarFoo->getUserBar'])
    ->getMock();

$locator
    ->expects($this->any())
    ->method('Foo->Bar->FooBar->BarFoo->getUserBar')
    ->will($this->returnValue($desiredObject));
可悲的是,这确实不起作用。我自己并不熟悉PHPUnit。有没有找到我尚未找到的解决方法?

2 个答案:

答案 0 :(得分:1)

就像我在评论中提到的那样,你可以存根一个容器,只要调用__get方法就会自动返回,并从你调用的实际方法中返回一个模拟对象到底。使用codeception's Stub component,这看起来像是:

$container = Stub::make(
    'Your\Container',
    [
        'getUserBar' => $returnMock,
    ]
);
Stub::update($container, ['__get' => $container]);//return itself on __get access

但正如你所说:这确实表明了你试图测试的代码存在一个更基本的问题。

答案 1 :(得分:0)

我之前找到了PHPUnit的解决方案,但只是碰巧有时间回答。

在PHPUnit中模拟我想要的东西实际上是可能的,使用PHP的Closure,这就是调用PHP中的匿名函数。

这是一个有点工作的PHPUnit示例

$classA = $this
    ->getMockBuilder('First\Class\To\Be\Mocked')
    ->disableOriginalConstructor()
    ->setMethods([
        'The',
        'names',
        'of',
        'desired',
        'methods',
    ])
    ->getMock();

$classB = $this
    ->getMockBuilder('Second\Class\To\Be\Mocked')
    ->disableOriginalConstructor()
    ->setMethods([
        'The',
        'names',
        'of',
        'desired',
        'methods',
    ])
    ->getMock();

$serviceLocator = $this
    ->getMockBuilder('Second\Class\To\Be\Mocked')
    ->disableOriginalConstructor()
    ->setMethods([
        '__get',
        'SomeMethod',
    ])
    ->getMock();

$serviceLocator
    ->expects($this->any())
    ->method('__get')
    ->will($this->returnCallback(function($name) use ($serviceLocator, $classA, $classB)
    {
        switch ($name)
        {
            case 'ClassA':
                return $classA;
            case 'ClassB':
                return $classB;
            default:
                return $serviceLocator;
        }
    }));

执行此代码时会给出所需的回报。

$serviceLocator->This->That->Them->ClassA将返回已定义的$classA变量,将ClassA更改为ClassB会使服务定位器返回$classB变量。

您可以使用回调执行任何操作,甚至可以通过参考参数模拟返回值。