首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何使用Symfony表单和数据转换器实现测试隔离?

如何使用Symfony表单和数据转换器实现测试隔离?
EN

Stack Overflow用户
提问于 2016-10-12 00:07:56
回答 1查看 572关注 0票数 20

注意:这是Symfony < 2.6版本,但我相信无论版本如何,总体问题都是一样的。

首先,考虑这个表单类型,它被设计为将一个或多个实体表示为隐藏字段(为简洁起见,省略了名称空间内容)

代码语言:javascript
复制
class HiddenEntityType extends AbstractType
{
    /**
     * @var EntityManager
     */
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if ($options['multiple']) {
            $builder->addViewTransformer(
                new EntitiesToPrimaryKeysTransformer(
                    $this->em->getRepository($options['class']),
                    $options['get_pk_callback'],
                    $options['identifier']
                )
            );
        } else {
            $builder->addViewTransformer(
                new EntityToPrimaryKeyTransformer(
                    $this->em->getRepository($options['class']),
                    $options['get_pk_callback']
                )
            );
        }
    }

    /**
     * See class docblock for description of options
     *
     * {@inheritdoc}
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'get_pk_callback' => function($entity) {
                return $entity->getId();
            },
            'multiple' => false,
            'identifier' => 'id',
            'data_class' => null,
        ));

        $resolver->setRequired(array('class'));
    }

    public function getName()
    {
        return 'hidden_entity';
    }

    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return 'hidden';
    }
}

这是可行的,简单明了,而且在很大程度上看起来就像您看到的向表单类型添加数据转换器的所有示例。直到你进入单元测试阶段。看到问题了吗?变形金刚是不能被嘲笑的。“等等!”你会说,“Symfony forms的单元测试是集成测试,它们应该确保转换器不会失败。in the documentation!”

此测试检查表单使用的数据转换器是否没有失败。只有当数据转换器抛出异常时,isSynchronized()方法才会设置为false

好的,那么你就接受了你不能隔离变压器的事实。别小题大作?

现在考虑在单元测试具有此类型字段的表单时会发生什么(假设已经在服务容器中定义并标记了HiddenEntityType )。

代码语言:javascript
复制
class SomeOtherFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('field', 'hidden_entity', array(
                'class' => 'AppBundle:EntityName',
                'multiple' => true,
            ));
    }

    /* ... */
}

现在进入问题。SomeOtherFormType的单元测试现在需要实现getExtensions(),才能使hidden_entity类型起作用。那么这看起来怎么样呢?

代码语言:javascript
复制
protected function getExtensions()
{
    $mockEntityManager = $this
        ->getMockBuilder('Doctrine\ORM\EntityManager')
        ->disableOriginalConstructor()
        ->getMock();

    /* Expectations go here */

    return array(
        new PreloadedExtension(
            array('hidden_entity' => new HiddenEntityType($mockEntityManager)),
            array()
        )
    );
}

看到这条评论在中间的位置了吗?是的,为了让它正常工作,HiddenEntityType的单元测试类中的所有模拟和期望现在都需要在这里复制。我对此不太满意,那么我有什么选择呢?

  1. 注入转换器作为选项之一

这将是非常直接的,并将使模仿更简单,但最终只是踢罐子在道路上。因为在这种情况下,new EntityToPrimaryKeyTransformer()只是从一个表单类型类移动到另一个。更不用说我觉得表单类型应该对系统的其余部分隐藏其内部复杂性。此选项意味着将复杂性推到表单类型的边界之外。

  • 将排序的转换器工厂注入到表单类型中

这是从方法中删除"newables“的一种更典型的方法,但我不能动摇这样做的感觉,即这样做只是为了使代码可测试,而不是实际上使代码变得更好。但如果这样做了,它将看起来像这样

$this->transformerFactory->createTransfomerForType($this,HiddenEntityType扩展AbstractType { /** * @var DataTransformerFactory */受保护的$transformerFactory;公共函数__construct(DataTransformerFactory $transformerFactory) {$this->$transformerFactory=buildForm;}公共函数FormBuilderInterface(FormBuilderInterface $builder,数组$options) { $builder->addViewTransformer( __construct $options););} /*类型不变*/ }

在我考虑工厂的实际外观之前,这感觉还可以。对于初学者来说,它需要注入实体管理器。但是,然后呢?如果我再往下看,这个被认为是通用的工厂可能需要各种依赖项来创建不同类型的数据转换器。这显然不是一个好的长期设计决策。然后呢?是否将其重新标记为EntityManagerAwareDataTransformerFactory?这里开始觉得乱七八糟了。

  • 的东西我没有想过...

有什么想法?经验?可靠的建议?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2016-10-18 22:58:02

首先,我几乎没有使用Symfony的经验。然而,我认为你错过了第三个选择。在有效使用遗留代码的过程中,Michael Feather概述了一种通过使用继承来隔离依赖项的方法(他称之为“提取和覆盖”)。

它是这样的:

代码语言:javascript
复制
class HiddenEntityType extends AbstractType
{
    /* stuff */

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if ($options['multiple']) {
            $builder->addViewTransformer(
                $this->createEntitiesToPrimaryKeysTransformer($options)
            );
        }
    }

    protected function createEntitiesToPrimaryKeysTransformer(array $options)
    {
        return new EntitiesToPrimaryKeysTransformer(
            $this->em->getRepository($options['class']),
            $options['get_pk_callback'],
            $options['identifier']
        );
    }
}

现在,为了进行测试,我们创建了一个扩展HiddenEntityType的新类FakeHiddenEntityType

代码语言:javascript
复制
class FakeHiddenEntityType extends HiddenEntityType {

    protected function createEntitiesToPrimaryKeysTransformer(array $options) {
        return $this->mock;
    }    

}

显然,$this->mock是您需要它成为的任何东西。

两个最突出的优点是不涉及工厂,因此复杂性仍然是封装的,并且这种更改实际上不会破坏现有代码。

缺点是这种技术需要额外的类。更重要的是,它需要一个知道被测类内部结构的类。

代码语言:javascript
复制
class HiddenEntityTypeTest extends TestCase
{

    private function createHiddenEntityType()
    {
        $mock = ...;  // Or pass as an argument

        return new class extends HiddenEntityType {

            protected function createEntitiesToPrimaryKeysTransformer(array $options)
            {
                return $mock;
            }    

        }
    }

    public function testABC()
    {
        $type = $this->createHiddenEntityType();
        /* ... */
    }

}
票数 12
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39981800

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档