Magento 2:单元测试范围内的代理和工厂生成
Magento 2 : Proxy and Factory generation in unit tests scope
我正在尝试为构造函数中注入了 Proxy Class 的 Magento 2(版本 2.2.0)Class 创建单元测试。
根据 Magento 文档,代理是生成的代码,就像工厂一样。
但是,在单元测试范围内(代码生成在 dev/tests/unit/tmp/generated 目录中),不会生成代理 class。仅生成工厂。
是否有任何原因导致未在单元测试范围内生成代理 Class?
假设:根据文档,Proxy 注入应该在 di.xml 配置文件中
<type name="FastLoading">
<arguments>
<argument name="slowLoading" xsi:type="object">SlowLoading\Proxy</argument>
</arguments>
</type>
而不是直接在构造函数中注入:
class FastLoading
{
protected $slowLoading;
public function __construct(
SlowLoading\Proxy $slowLoading
){
$this->slowLoading = slowLoading;
}
}
那么,直接在构造函数中注入代理 Class 是一种不好的做法吗?
单元测试范围内工厂生成的另一个问题,假设生成以下工厂:
// dev/tests/unit/tmp/generated/code/Magento/Framework/Api/SearchCriteriaBuilderFactory.php
namespace Magento\Framework\Api;
class SearchCriteriaBuilderFactory
{
public function create(array $data = [])
{
}
}
生成的 create() 方法为空的原因是什么 return null in unit test scope ?
谢谢。
- 首先,假设应该在 xml 中配置代理 class 而不是在构造函数中注入它是错误的,因为您无法配置构造函数尚未期望的参数。因此两者是互补的。
You can configure the class constructor arguments in your di.xml in the argument node. The object manager injects these arguments into the class during creation. The name of the argument configured in the XML file must correspond to the name of the parameter in the constructor in the configured class.
其次,在生产中您使用不同的对象管理器和不同的依赖配置。当您在某个阶段使用 developer/production 对象管理器创建工厂时,会注入负责生成工厂的动态工厂对象(见下图)
分别对于开发者模式动态class是
vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php 和
生产方式
vendor/magento/framework/ObjectManager/Factory/Dynamic/Production.php
对于 UT 的 ObjectManager,它或多或少是一个带有一些附加实用程序的模拟包装器,因此它不会生成任何真实的 class。实际上,::getObject() 将在第 161 行代码上抛出异常。如您所见,那是因为除了一些反射魔法之外别无其他。
关于代理问题,根据 ad.1,您的解决方案甚至不可能,而且由于与工厂相同的原因,未生成代理 class。
从 UT 的角度来看,我无法想象您需要任何自动生成的 class 的情况。所有依赖项都应该被模拟和生成 class 永远不会被直接测试。对于工厂或代理,创建模拟如下:
$mockSearachCriteriaBuilder = $this->getMockBuilder(Magento\Framework\Api\SearchCriteriaBuilderFactory::class)->disableOriginalConstructor()->setMethods([set_your_methods_stubs]->getMock()
然后将其作为依赖项注入到 class 的构造函数中进行测试,例如
$this->om = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->om->getObject('your\class\name', ['searchCriteriaBuilder' => $this->mockSearchCriteriaBuilder];
这只是一个示例,但它表明即使您的问题很有趣,问题也不存在,因为真正的解决方案在于完全不同的方法。
更新:
不,模拟不需要 class 的存在,类型就是全部,所以如果模拟可以像给定类型一样运行,那么源 class 不必存在。
class FactoryTest extends \PHPUnit_Framework_TestCase
{
public function testSomething()
{
$mock = $this->getMock(UnknownClass::class, ['test']);
$mock->expects($this->once())->method('test')->willReturn(true);
$this->assertTrue($mock->test());
}
}
上面的测试将通过,但没有像 UnknownClass 这样的东西。此外,没有 createMock 方法。单元测试就是隔离。如果测试需要超过 class under test 那么它违反了这个原则。这就是嘲弄派上用场的地方。
我正在尝试为构造函数中注入了 Proxy Class 的 Magento 2(版本 2.2.0)Class 创建单元测试。 根据 Magento 文档,代理是生成的代码,就像工厂一样。
但是,在单元测试范围内(代码生成在 dev/tests/unit/tmp/generated 目录中),不会生成代理 class。仅生成工厂。
是否有任何原因导致未在单元测试范围内生成代理 Class?
假设:根据文档,Proxy 注入应该在 di.xml 配置文件中
<type name="FastLoading">
<arguments>
<argument name="slowLoading" xsi:type="object">SlowLoading\Proxy</argument>
</arguments>
</type>
而不是直接在构造函数中注入:
class FastLoading
{
protected $slowLoading;
public function __construct(
SlowLoading\Proxy $slowLoading
){
$this->slowLoading = slowLoading;
}
}
那么,直接在构造函数中注入代理 Class 是一种不好的做法吗?
单元测试范围内工厂生成的另一个问题,假设生成以下工厂:
// dev/tests/unit/tmp/generated/code/Magento/Framework/Api/SearchCriteriaBuilderFactory.php
namespace Magento\Framework\Api;
class SearchCriteriaBuilderFactory
{
public function create(array $data = [])
{
}
}
生成的 create() 方法为空的原因是什么 return null in unit test scope ?
谢谢。
- 首先,假设应该在 xml 中配置代理 class 而不是在构造函数中注入它是错误的,因为您无法配置构造函数尚未期望的参数。因此两者是互补的。
You can configure the class constructor arguments in your di.xml in the argument node. The object manager injects these arguments into the class during creation. The name of the argument configured in the XML file must correspond to the name of the parameter in the constructor in the configured class.
其次,在生产中您使用不同的对象管理器和不同的依赖配置。当您在某个阶段使用 developer/production 对象管理器创建工厂时,会注入负责生成工厂的动态工厂对象(见下图)
分别对于开发者模式动态class是 vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php 和 生产方式 vendor/magento/framework/ObjectManager/Factory/Dynamic/Production.php 对于 UT 的 ObjectManager,它或多或少是一个带有一些附加实用程序的模拟包装器,因此它不会生成任何真实的 class。实际上,::getObject() 将在第 161 行代码上抛出异常。如您所见,那是因为除了一些反射魔法之外别无其他。
关于代理问题,根据 ad.1,您的解决方案甚至不可能,而且由于与工厂相同的原因,未生成代理 class。
从 UT 的角度来看,我无法想象您需要任何自动生成的 class 的情况。所有依赖项都应该被模拟和生成 class 永远不会被直接测试。对于工厂或代理,创建模拟如下:
$mockSearachCriteriaBuilder = $this->getMockBuilder(Magento\Framework\Api\SearchCriteriaBuilderFactory::class)->disableOriginalConstructor()->setMethods([set_your_methods_stubs]->getMock()
然后将其作为依赖项注入到 class 的构造函数中进行测试,例如
$this->om = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->om->getObject('your\class\name', ['searchCriteriaBuilder' => $this->mockSearchCriteriaBuilder];
这只是一个示例,但它表明即使您的问题很有趣,问题也不存在,因为真正的解决方案在于完全不同的方法。
更新: 不,模拟不需要 class 的存在,类型就是全部,所以如果模拟可以像给定类型一样运行,那么源 class 不必存在。
class FactoryTest extends \PHPUnit_Framework_TestCase
{
public function testSomething()
{
$mock = $this->getMock(UnknownClass::class, ['test']);
$mock->expects($this->once())->method('test')->willReturn(true);
$this->assertTrue($mock->test());
}
}
上面的测试将通过,但没有像 UnknownClass 这样的东西。此外,没有 createMock 方法。单元测试就是隔离。如果测试需要超过 class under test 那么它违反了这个原则。这就是嘲弄派上用场的地方。