我最近接手了一个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。有没有找到我尚未找到的解决方法?
答案 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
变量。
您可以使用回调执行任何操作,甚至可以通过参考参数模拟返回值。