2016-10-11 12 views
19

Lưu ý: Đây là Symfony < 2,6 nhưng tôi tin rằng các vấn đề tổng thể cũng áp dụng không phân biệt phiên bảnLàm thế nào để đạt được sự cô lập thử nghiệm với các biểu mẫu Symfony và các máy biến dữ liệu?

Để bắt đầu, hãy xem xét loại hình thức này được thiết kế để đại diện cho một-or-nhiều thực thể như một lĩnh vực tiềm ẩn (namespace thứ bỏ qua cho ngắn gọn)

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

này hoạt động, đó là đơn giản, và cho hầu hết các phần trông giống như giống như tất cả các ví dụ mà bạn nhìn thấy để thêm biến dữ liệu đến một kiểu mẫu. Cho đến khi bạn nhận được để kiểm tra đơn vị. Xem vấn đề? Các máy biến áp không thể bị chế nhạo. "Nhưng chờ đã!" bạn nói, "Bài kiểm tra đơn vị cho các biểu mẫu của Symfony là các bài kiểm tra tích hợp, chúng phải đảm bảo rằng các biến không thành công. Thậm chí nói như vậy là in the documentation!"

Kiểm tra này kiểm tra xem không có biến số dữ liệu nào của bạn được sử dụng theo biểu mẫu không thành công. Phương thức isSynchronized() chỉ được đặt thành false nếu dữ liệu biến áp ném một ngoại lệ

Ok, vậy thì bạn sống với thực tế bạn không thể cách ly máy biến áp. Không phải vấn đề lớn?

Bây giờ xem xét những gì sẽ xảy ra khi đơn vị thử nghiệm một hình thức mà có một trường thuộc loại này (giả sử rằng HiddenEntityType đã được định nghĩa & gắn thẻ trong container dịch vụ)

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

    /* ... */ 
} 

Bây giờ đi vào vấn đề. Kiểm tra đơn vị cho SomeOtherFormType hiện cần triển khai getExtensions() để loại hidden_entity hoạt động. Vậy nó trông như thế nào?

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

Xem nơi nhận xét đó nằm ở chính giữa? Vâng, do đó, để điều này hoạt động chính xác, tất cả các mocks và kỳ vọng trong lớp kiểm tra đơn vị cho HiddenEntityType giờ đây cần được sao chép ở đây một cách hiệu quả. Tôi không đồng ý với điều này, vì vậy lựa chọn của tôi là gì?

  1. Tiêm biến áp là một trong những lựa chọn

    này sẽ rất đơn giản và có thể làm cho chế giễu đơn giản hơn, nhưng cuối cùng chỉ đá lon xuống đường. Bởi vì trong trường hợp này, new EntityToPrimaryKeyTransformer() sẽ chỉ chuyển từ một loại loại biểu mẫu này sang lớp loại khác. Chưa kể rằng tôi cảm thấy các loại biểu mẫu nên ẩn nội dung phức tạp của chúng khỏi phần còn lại của hệ thống. Tùy chọn này có nghĩa là đẩy sự phức tạp đó ra ngoài ranh giới của loại biểu mẫu.

  2. Tiêm một nhà máy biến áp các loại thành các loại hình thức

    Đây là một cách tiếp cận điển hình hơn để loại bỏ "newables" từ bên trong một phương pháp, nhưng tôi không thể rung cảm giác rằng điều này đang được thực hiện chỉ để làm cho mã có thể kiểm tra và không thực sự làm cho mã tốt hơn. Nhưng nếu điều đó được thực hiện, nó sẽ trông giống như thế này

    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 */ 
    } 
    

    Điều này cảm thấy ok cho đến khi tôi xem xét nhà máy sẽ trông như thế nào. Nó sẽ cần quản lý thực thể được tiêm, cho người mới bắt đầu. Nhưng những gì sau đó?Nếu tôi nhìn xa hơn xuống con đường, nhà máy được cho là chung chung này có thể cần tất cả các loại phụ thuộc để tạo ra các máy biến áp dữ liệu thuộc các loại khác nhau. Đó rõ ràng không phải là một quyết định thiết kế lâu dài tốt. Vậy thì sao? Đánh dấu lại tên này là EntityManagerAwareDataTransformerFactory? Nó bắt đầu cảm thấy lộn xộn ở đây.

  3. Stuff Tôi không nghĩ đến việc ...

Suy nghĩ? Kinh nghiệm? Lời khuyên vững chắc?

Trả lời

11

Trước hết, tôi không có kinh nghiệm với Symfony. Tuy nhiên, tôi nghĩ bạn đã bỏ lỡ một lựa chọn thứ ba ở đó. Khi làm việc hiệu quả với mã Legacy, Michael Feathers vạch ra một cách để cô lập các phụ thuộc bằng cách sử dụng thừa kế (ông gọi nó là "Extract and Override").

Nó đi như thế này:

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

Bây giờ để kiểm tra, bạn tạo một lớp mới, FakeHiddenEntityType, mà kéo dài HiddenEntityType.

class FakeHiddenEntityType extends HiddenEntityType { 

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

} 

Trường hợp $this->mock rõ ràng là bất cứ điều gì bạn cần.

Hai ưu điểm nổi bật nhất là không có nhà máy tham gia, do đó sự phức tạp vẫn được đóng gói, và hầu như không có khả năng thay đổi này phá vỡ mã hiện có.

Điểm bất lợi là kỹ thuật này yêu cầu một lớp phụ. Quan trọng hơn, nó đòi hỏi một lớp học biết về nội bộ của lớp đang được kiểm tra.


Để tránh lớp bổ sung, hoặc ẩn lớp phụ, bạn có thể đóng gói trong một hàm, tạo lớp ẩn danh thay thế (hỗ trợ cho các lớp ẩn danh được thêm vào 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

Cảm ơn bạn đã trả lời và thậm chí nhiều hơn thế, cảm ơn bạn đã làm rõ nguồn gốc của đề xuất của bạn. Nó luôn luôn tốt đẹp để thêm sách mới vào danh sách đọc. –

Các vấn đề liên quan