2016-10-11 10 views
19

注:これはsymfonyの< 2.6であるが、私は隠しフィールドとして、一または-複数のエンティティを表現するために設計されたこのフォームの種類を考慮し、同じ全体的な問題を開始するにはバージョンに関係なくSymfonyフォームとデータトランスフォーマーでテスト分離を達成するには?

の適用と信じて(簡潔にするため省略した名前空間のもの)

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フォームの単体テストは統合テストであり、トランスフォーマーが失敗しないことを確認することになっています。でもそうですin the documentation!」

このテストでは、 というフォームで使用されているデータトランスフォーマがどれも故障していないことを確認します。データ 変圧器は例外

[OK]をスローした場合IsSynchronizedのトピック()メソッドは、だけなので、あなたは、あなたが変圧器を隔離することができないという事実と一緒に暮らす、falseに設定されています。大きな問題ではない?

今、このタイプのフィールドを有するフォームをテストユニット(HiddenEntityTypeサービスコンテナにタグ付けされた&を定義されていると仮定)

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

    /* ... */ 
} 

今問題に入ったときに何が起こるかを考えます。 SomeOtherFormTypeのユニットテストは、hidden_entityタイプが機能するために今やgetExtensions()を実装する必要があります。だから、どうやって見えるの?

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の単体テスト・クラスにあるすべてのモックと期待は、ここでは事実上ここに複製する必要があります。私はこれでOKではないので、私の選択肢は何ですか?

  1. はオプション

    これは非常に簡単だろうと単純にモックなるだろうの一つとして、変圧器を注入するが、最終的にちょうど道缶けり。このシナリオでは、new EntityToPrimaryKeyTransformer()は1つのフォームタイプクラスから別のフォームタイプクラスに移動するだけなので、私はフォームの種類がなら、はシステムの残りの部分から隠蔽しなければならないと感じていることは言うまでもありません。このオプションは、その複雑さをフォームタイプの境界の外に押し出すことを意味します。

  2. これはメソッド内から「newables」を除去することに、より一般的なアプローチですが、私はこれがちょうどに行われているという感覚を振り払うことができないフォームタイプに

    を一種の変圧器工場を注入しますコードをテスト可能にし、実際にコードを改善していない。しかし、それが完了した場合、このようなものになります。

    class HiddenEntityType extends AbstractType 
    { 
        /** 
        * @var DataTransformerFactory 
        */ 
        protected $transformerFactory; 
    
        public function __construct(DataTransformerFactory $transformerFactory) 
        { 
         $this->transformerFactory = $transformerFactory; 
        } 
    
        public function buildForm(FormBuilderInterface $builder, array $options) 
        { 
         $builder->addViewTransformer(
          $this->transformerFactory->createTransfomerForType($this, $options); 
         ); 
        } 
    
        /* Rest of type unchanged */ 
    } 
    

    これは工場が実際にどのように見えるかを検討するまでわかります。初心者には、エンティティマネージャを注入する必要があります。しかし、それは何ですか?道路をさらに見下ろすと、おそらくジェネリックな工場では、さまざまな種類のデータトランスを作成するためにあらゆる種類の依存関係が必要になる可能性があります。それは明らかに良い長期的な設計決定ではありません。だから何? EntityManagerAwareDataTransformerFactoryとラベルを付け直しますか?それはここで乱雑に感じ始めている。

  3. スタッフ私は...の

思考を考えていませんよ?経験?確かなアドバイス?

答えて

11

まず、私はSymfonyの経験が一番少ないです。しかし、私はそこに3番目のオプションがないと思う。レガシーコードでの作業では、継承を使用して依存関係を分離する方法を概説しています(彼はそれを「抽出と上書き」と呼んでいます)。

それはこのようになります:あなたはHiddenEntityTypeを拡張する新しいクラス、FakeHiddenEntityTypeを作成、テストするために今すぐ

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'] 
     ); 
    } 
} 

。明らかに$this->mockは、あなたがそれをする必要が何である

class FakeHiddenEntityType extends HiddenEntityType { 

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

} 

2つの最も顕著な利点は、工場が関与していないことです。したがって、複雑さはまだカプセル化されており、この変更によって既存のコードが破損する可能性はほとんどありません。

この手法では、余分なクラスが必要であるという欠点があります。さらに重要なのは、テスト対象のクラスの内部について知っているクラスが必要です。


余分なクラスを避けるため、あるいはむしろ余分なクラスを非表示にするには、一つは(匿名クラスのサポートはPHP 7で追加されました)代わりに、匿名クラスを作成し、関数内でそれをカプセル化することができます。

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(); 
     /* ... */ 
    } 

} 
+1

さらに、そう返事してくれてありがとうとは、あなたの勧告の明確なソースを作るためにあなたに感謝。読書リストに新しい本を追加することは常にいいです。 –

関連する問題