2016-10-11 2 views
19

Hinweis: Dies ist Symfony < 2.6, aber ich glaube, die gleiche Gesamt Problem unabhängig von der Version giltWie wird eine Testisolierung mit Symfony-Formularen und Datentransformatoren erreicht?

dieses Formulartyp zu starten, betrachten, die ein-oder-mehr Einheiten zu repräsentieren als verstecktes Feld ausgelegt ist (Namespace Material der Kürze halber weggelassen)

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

Dies funktioniert, dann ist es einfach, und zum größten Teil sieht aus wie wie alle Beispiele, die Sie sehen für das Hinzufügen von Datenwandlern zu einem Formulartyp. Bis Sie zum Komponententest kommen. Siehst du das Problem? Die Transformatoren können nicht verspottet werden. "Aber warte!" Sie sagen: "Komponententests für Symfony-Formulare sind Integrationstests, mit denen sichergestellt werden soll, dass die Transformatoren nicht ausfallen. Das sagt sogar in the documentation!"

Dieser Test überprüft, dass keiner Ihrer Datentransformatoren im Formular fehlgeschlagen ist. Die isSynchronized() Methode wird nur auf falsch gesetzt, wenn ein Daten Transformator eine Ausnahme

Ok wirft, so dann man mit der Tatsache leben, nicht die Transformatoren isolieren kann. Keine große Sache?

Betrachten wir nun, was passiert, wenn das Gerät ein Formular zu testen, die ein Feld dieses Typs hat (unter der Annahme, dass HiddenEntityType hat & im Service-Container markiert definiert)

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

    /* ... */ 
} 

tritt nun das Problem. Der Komponententest für SomeOtherFormType muss jetzt getExtensions() implementieren, damit der Typ hidden_entity funktioniert. Wie sieht das aus?

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

Sehen Sie, wo dieser Kommentar ist in der Mitte? Ja, damit dies richtig funktioniert, müssen alle Mocks und Erwartungen, die in der Unit-Test-Klasse für die HiddenEntityType vorliegen, nun effektiv dupliziert werden. Ich bin damit nicht einverstanden, also was sind meine Optionen?

  1. Injizieren des Transformators als eine der Optionen

    Dies würde sehr einfach sein und einfacher verspotten würde, aber letztendlich schlägt nur die Dose die Straße hinunter. In diesem Szenario würde new EntityToPrimaryKeyTransformer() nur von einer Formulartypklasse in eine andere wechseln. Ganz zu schweigen davon, dass ich das Gefühl habe, dass die Formulartypen ihre interne Komplexität vor dem Rest des Systems verbergen sollten. Diese Option bedeutet, dass diese Komplexität über die Grenzen des Formulartyps hinausgeht.

  2. Injizieren einen Transformator Fabrik von Sorten in den Formulartyp

    Dies ist ein typischer Ansatz „newables“ innerhalb einer Methode zu entfernen, aber ich kann das Gefühl nicht los, dass dies nur getan wird, um zu mache den Code testbar und mache den Code nicht wirklich besser. Aber wenn das gemacht wäre, würde es ungefähr so ​​aussehen:

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

    Das fühlt sich gut an, bis ich überlege, wie die Fabrik tatsächlich aussehen wird. Es wird den Entity Manager zuerst injiziert brauchen. Aber was dann?Wenn ich weiter nach unten schaue, könnte diese vermeintlich generische Fabrik alle möglichen Abhängigkeiten für die Erstellung von Datentransformatoren verschiedener Art benötigen. Das ist eindeutig keine gute langfristige Designentscheidung. Also was dann? Re-Label dies als EntityManagerAwareDataTransformerFactory? Es fühlt sich hier unordentlich an. Ich

  3. Stuff ich denke nicht an ...

Gedanken? Erfahrungen? Solide Beratung?

Antwort

11

Zunächst habe ich fast keine Erfahrung mit Symfony. Ich denke jedoch, dass Sie dort eine dritte Option verpasst haben. In Effektiver Arbeit mit Legacy-Code umreißt Michael Feathers eine Möglichkeit, Abhängigkeiten durch Vererbung zu isolieren (er nennt es "Extract and Override").

Es geht so:

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

Jetzt zu testen, eine neue Klasse erstellen, FakeHiddenEntityType, die HiddenEntityType erstreckt.

class FakeHiddenEntityType extends HiddenEntityType { 

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

} 

Wo $this->mock ist offensichtlich, was auch immer Sie es brauchen.

Die zwei hervorstechendsten Vorteile sind, dass es keine Fabriken gibt, daher ist die Komplexität immer noch gekapselt, und es gibt praktisch keine Chance, dass diese Änderung den bestehenden Code bricht.

Der Nachteil ist, dass diese Technik eine zusätzliche Klasse erfordert. Noch wichtiger ist, dass eine Klasse erforderlich ist, die die Interna der zu testenden Klasse kennt.


Um die Extraklasse zu vermeiden, oder besser gesagt die Extraklasse verstecken, man es in einer Funktion kapseln könnte, stattdessen eine anonyme Klasse erstellen (Unterstützung für anonyme Klassen in PHP 7 hinzugefügt).

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

Vielen Dank für die Antwort und noch mehr, danke für die klare die Quelle Ihrer Empfehlung. Es ist immer schön, neue Bücher zur Leseliste hinzuzufügen. –

Verwandte Themen