我最近接手了一个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不是很熟练。有没有我还没有找到的变通办法?
正如我在评论中提到的,您可以存根一个容器,该容器在调用__get
方法时返回自身,并最终从您调用的实际方法返回一个模拟对象。使用codeception的Stub
组件,这可能看起来像:
$container = Stub::make(
'Your'Container',
[
'getUserBar' => $returnMock,
]
);
Stub::update($container, ['__get' => $container]);//return itself on __get access
但正如您所说:这确实表明您试图测试的代码存在更根本的问题。
我早些时候找到了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
变量。
您可以使用回调做任何事情,甚至可以通过引用参数模拟返回值。