2015-01-12 32 views
5

Attualmente sto affrontando un grosso problema. Durante un test unitario, utilizzo i dispositivi all'inizio del test per aggiungere dati in un database (tutto funziona correttamente) e vorrei usare lo stesso codice del dispositivo per aggiungere dati durante il test. Esempio:Symfony2, fixtures e unit test. Aggiungi dati di test durante il test in una relazione ManyToMany

Entity Persona:

class Person { 
  
    /** 
     * @var integer 
     * 
     * @ORM\Column(name="id", type="integer") 
     * @ORM\Id 
     * @ORM\GeneratedValue(strategy="AUTO") 
     */ 
    private $id; 
  
    [...] 
  
    /** 
     * @ORM\ManyToMany(targetEntity="Family", cascade={"persist"}) 
     * @Assert\Count(min = "1") 
     */ 
    private $families; 
  
    public function __construct() { 
        $this->families = new \Doctrine\Common\Collections\ArrayCollection(); 
    } 
  
    public function addFamily(Family $family) { 
        $this->families[] = $family; 
        return $this; 
    } 
  
    [...] 
  
} 

Entity Famiglia:

class Family { 
  
    /** 
     * @var integer 
     * 
     * @ORM\Column(name="id", type="integer") 
     * @ORM\Id 
     * @ORM\GeneratedValue(strategy="AUTO") 
     */ 
    private $id; 
  
    /** 
     * @var string 
     * @Gedmo\Slug(fields={"name"}) 
     * @ORM\Column(name="slug", type="string", length=255, unique=true) 
     */ 
    private $slug; 
  
    [...] 
  
} 

Infissi: Test

class Fixture1 implements FixtureInterface, ContainerAwareInterface { 
    /** 
     * @var Family[] 
     */ 
    public $families = array(); 
  
    public function setContainer(ContainerInterface $container = null) { 
        $this->container = $container; 
    } 
  
    public function load(ObjectManager $manager) { 
        $this->manager = $manager; 
        $SmithFamily= $this->addFamily("Smith"); 
        $this->addPerson("Will", "Smith", array($SmithFamily)); 
    } 
  
    public function addFamily($name) { 
        $family = new Family(); 
        $family->setName($name); 
        $this->manager->persist($family); 
        $this->manager->flush(); 
        $this->families[$name] = $family; 
        return $family; 
    } 
      
    public function addPerson($firstName, $lastName, array $families = array()) { 
        $person = new Person(); 
        $person->setFirstname($firstName); 
        $person->setLastname($lastName); 
        foreach ($families as $family) { 
            $person->addFamily($family); 
        } 
          
        $this->manager->persist($person); 
        $this->manager->flush(); 
        return $person; 
    } 
} 

Unità:

class PersonTest extends WebTestCase { 
  
    public $fixtures; 
  
    protected function setUp() { 
        $client = self::createClient(); 
        $container = $client->getKernel()->getContainer(); 
        $em = $container->get('doctrine')->getManager(); 
   
        // Purge tables 
        $purger = new \Doctrine\Common\DataFixtures\Purger\ORMPurger($em); 
        $executor = new \Doctrine\Common\DataFixtures\Executor\ORMExecutor($em, $purger); 
        $executor->purge(); 
   
        // Load fixtures 
        $loader = new \Doctrine\Common\DataFixtures\Loader; 
        $this->fixtures = new Fixture1(); 
        $this->fixtures->setContainer($container); 
        $loader->addFixture($this->fixtures); 
        $executor->execute($loader->getFixtures()); 
          
        parent::setUp(); 
    } 
  
    public function test1() { 
        $this->fixtures->addPerson("James", "Smith", array($this->fixtures->families['Smith'])); 
     [...] 
    } 
  
} 

Quando eseguo il test, ottengo questo errore:

Doctrine \ DBAL \ Exception \ UniqueConstraintViolationException: Si è verificata un'eccezione durante l'esecuzione di 'INSERT INTO famiglia (lumaca, nome, create, last_updated_date, deleteddate) VALORI (?,?,?,?,?) 'con parametri ["smith", "Smith", "2015-01-10 13:59:28", "01-01-01 13:59:28" , null]: SQLSTATE [23000]: Integrità violazione del vincolo: 1062 duplicata du champ 'Smith' pour la clef 'UNIQ_A5E6215B989D9B62'

Questo errore indica che il campo "slug" deve essere univoco. Prova ad aggiungere una nuova famiglia, nonostante la famiglia esista già. Dovrebbe solo aggiungere una relazione tra la nuova Persona (James Smith) e La famiglia. Non capisco perché! Qualche idea?

risposta

0

Colgo un passo alla volta, qui segue un po 'più di quanto chiesto, ma spero che vi aiuterà a scrivere i test più pulite come ho visto quelle cattive abbastanza i miei luoghi di lavoro:

Innanzitutto, questo non è un test unitario, solo per quello che sai. Si chiama test funzionale perché stai testando l'intera funzionalità del sistema e non una piccola unità che sarebbe una classe.

In secondo luogo, non creare un nuovo client (chiamata createClient) solo per ottenere un contenitore. Più di solito non è necessario più di un cliente per ogni caso di test. Se si desidera veramente crearlo nel metodo setUp, memorizzarlo nel campo TestCase (creare un campo protetto nella classe TestCase che si utilizzerà per esso) e memorizzarlo nel metodo setUp in modo da poterlo utilizzare nel caso di test. Ciò è necessario perché a un certo punto introdurrà bug nella tua suite di test creando istanze di più di un contenitore (createClient crea un'istanza di un nuovo contenitore ogni volta che lo chiami) e con più EntitiManager ad esempio. Poi ad un certo punto avrai alcune entità che appartengono a un EntityManager e altre a un altro (che è possibile gestire ma se non sai come funziona Doctrine è meglio non affrontarlo per te).

In terzo luogo, non è necessario alcun caricatore di dispositivi nel proprio esempio. Inoltre, non è necessario chiamare lo purge perché viene chiamato automaticamente da executor. Ecco come potrebbe essere il tuo codice in setUp:

$this->client = self::createClient(); 
$em = $this->getKernel()->getContainer()->get("doctrine.orm.default_entity_manager"); 
$executor = new ORMExecutor($em, new ORMPurger()); 
$executor->execute([new Fixture1()]); 

parent::setUp(); 

Penso che sia molto più semplice in questo modo.

Inoltre, non sono sicuro del motivo per cui è necessario che il dispositivo sia ContainerAware, poiché non si utilizza il contenitore per quanto posso vedere e nella maggior parte dei casi non è necessario, in realtà.

In quarto luogo, non archiviare mai EntityManager nell'istanza di Fixture. Dovrebbe superare il metodo load, usarlo per caricare tutto e dimenticarlo completamente.

Provare a cambiare i metodi di fissaggio in modo che si aspetti un gestore come argomento invece di utilizzarlo come campo e chiamare il (new Fixture1)->addPerson($this->client->getKernel()->getContainer()->get("doctrine.orm.default_entity_manager"), "James", "Smith", $existingFamily); dal metodo di prova. Sembra che a un certo punto si usino diversi manager, quindi il secondo non sa ancora della famiglia e, quando si imposta cascade={persist} per le famiglie in Persona, esso persiste automaticamente e tenta di reinserire.