嵌套容器的测试和模拟结果
Testing and mocking result of a nested container
我最近接手了一个 PHP 项目,其中几乎没有测试。这个项目比较大,class大部分都很小,但是有一个大问题。
有一个服务定位器巧妙地隐藏在受保护或私有变量中 DI
,因此程序员认为他们在做正确的事情,它充当单例并且几乎传递给每个单例 class. class,在 return 中使用它来检索依赖项。
上周四,我创建了一个由 3 人组成的新团队,其新职责是专注于测试及其自动化,今天终于有一个人来找我,提出了我担心的问题。一个问题,我不知道答案。
David, how am I suppose to mock the result of the method which is
hidden deeply within the 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
方法时 returns 本身,并且 returns 来自你最后调用的实际方法的模拟对象。使用 codeception's Stub
component,这可能类似于:
$container = Stub::make(
'Your\Container',
[
'getUserBar' => $returnMock,
]
);
Stub::update($container, ['__get' => $container]);//return itself on __get access
但正如您所说:这确实表明您要测试的代码存在更根本的问题。
我之前找到了PHP单元的解决方案,只是刚好有时间回答。
在 PHPUnit 中模拟我想要的东西实际上是可能的,使用 PHP 的 Closure
,这就是 PHP 中调用的匿名函数。
这是一个小工作PHP单元示例
$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;
}
}));
此代码在执行时将给出所需的 returns。
$serviceLocator->This->That->Them->ClassA
会 return 定义的 $classA
变量,将 ClassA 更改为 ClassB 会使服务定位器 return 变成 $classB
变量。
使用回调几乎可以做任何事情,甚至可以通过引用参数模拟 return 值。
我最近接手了一个 PHP 项目,其中几乎没有测试。这个项目比较大,class大部分都很小,但是有一个大问题。
有一个服务定位器巧妙地隐藏在受保护或私有变量中 DI
,因此程序员认为他们在做正确的事情,它充当单例并且几乎传递给每个单例 class. class,在 return 中使用它来检索依赖项。
上周四,我创建了一个由 3 人组成的新团队,其新职责是专注于测试及其自动化,今天终于有一个人来找我,提出了我担心的问题。一个问题,我不知道答案。
David, how am I suppose to mock the result of the method which is hidden deeply within the
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
方法时 returns 本身,并且 returns 来自你最后调用的实际方法的模拟对象。使用 codeception's Stub
component,这可能类似于:
$container = Stub::make(
'Your\Container',
[
'getUserBar' => $returnMock,
]
);
Stub::update($container, ['__get' => $container]);//return itself on __get access
但正如您所说:这确实表明您要测试的代码存在更根本的问题。
我之前找到了PHP单元的解决方案,只是刚好有时间回答。
在 PHPUnit 中模拟我想要的东西实际上是可能的,使用 PHP 的 Closure
,这就是 PHP 中调用的匿名函数。
这是一个小工作PHP单元示例
$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;
}
}));
此代码在执行时将给出所需的 returns。
$serviceLocator->This->That->Them->ClassA
会 return 定义的 $classA
变量,将 ClassA 更改为 ClassB 会使服务定位器 return 变成 $classB
变量。
使用回调几乎可以做任何事情,甚至可以通过引用参数模拟 return 值。