2012-05-28 10 views
41

Soy bastante nuevo en el mundo de las pruebas y quiero asegurarme de estar en el camino correcto.¿Cómo configurar las pruebas de unidad pesada de base de datos en Symfony2 usando PHPUnit?

Estoy tratando de pruebas de unidad de configuración en un proyecto Symfony2 utilizando PHPUnit .

PHPUnit funciona y las sencillas pruebas predeterminadas del controlador funcionan bien. (Sin embargo, esto no es acerca de las pruebas funcionales, pero unidad de prueba de mi solicitud.)

Mi proyecto se basa en gran medida de las interacciones de bases de datos, sin embargo, y por lo que yo entiendo de phpunit's documentation, yo debería establecer una clase basada en \PHPUnit_Extensions_Database_TestCase, a continuación, crear accesorios para mi DB y trabajar desde allí.

embargo, Symfony2 sólo ofrece una clase WebTestCase que sólo se extiende desde \PHPUnit_Framework_TestCase fuera de la caja.

Así que estoy en lo cierto al suponer que yo debería crear mis propios DataBaseTestCase que en su mayoría copias WebTestCase, única diferencia de que se extienda desde \PHPUnit_Extensions_Database_TestCase y pone en práctica todas sus métodos abstractos?

¿O existe otro flujo de trabajo "incorporado" recomendado para symfony2 con respecto a las pruebas centradas en la base de datos?

Como quiero asegurarme de que mis modelos almacenen y recuperen los datos correctos, no quiero terminar probando los detalles de doctrine por accidente.

+0

Estoy trabajando en el mismo problema. ¿Alguna suerte hasta ahora? –

+0

@JasonSwett Nope. Acabo de comenzar una recompensa debido a la falta de una respuesta satisfactoria. – k0pernikus

Respuesta

2

tl; dr:

  • Si y sólo si quieres ir todo el recorrido de pruebas funcionales, entonces te recomiendo mirando hacia arriba Sgoettschkes's answer.
  • Si quieres unidad de probar la aplicación y tiene que probar el código que interactúa con la base de datos, ya sea leyendo o saltar directamente a symfony2 docs


Había ciertos aspectos de mi pregunta original que hacen que sea clara que mi comprensión de las diferencias entre pruebas unitarias y pruebas funcionales era deficiente. (Como ya he escrito, quiero probar la aplicación de manera unitaria, pero también estaba hablando de la prueba del controlador al mismo tiempo, y esas son pruebas funcionales por definición).

Las pruebas unitarias solo tienen sentido para los servicios y no para los repositorios. Y esos servicios pueden usar burlas del administrador de la entidad.

Mi caso de uso real para mi aplicación estaba, de hecho, muy bien reflejado en los documentos symfony2 en how to test code that interacts with the databse.Proporcionan este ejemplo para una prueba de servicio: Clase

Servicio:

use Doctrine\Common\Persistence\ObjectManager; 

class SalaryCalculator 
{ 
    private $entityManager; 

    public function __construct(ObjectManager $entityManager) 
    { 
     $this->entityManager = $entityManager; 
    } 

    public function calculateTotalSalary($id) 
    { 
     $employeeRepository = $this->entityManager 
      ->getRepository('AppBundle:Employee'); 
     $employee = $employeeRepository->find($id); 

     return $employee->getSalary() + $employee->getBonus(); 
    } 
} 

Servicio clase de prueba:

namespace Tests\AppBundle\Salary; 

use AppBundle\Salary\SalaryCalculator; 
use AppBundle\Entity\Employee; 
use Doctrine\ORM\EntityRepository; 
use Doctrine\Common\Persistence\ObjectManager; 

class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase 
{ 
    public function testCalculateTotalSalary() 
    { 
     // First, mock the object to be used in the test 
     $employee = $this->getMock(Employee::class); 
     $employee->expects($this->once()) 
      ->method('getSalary') 
      ->will($this->returnValue(1000)); 
     $employee->expects($this->once()) 
      ->method('getBonus') 
      ->will($this->returnValue(1100)); 

     // Now, mock the repository so it returns the mock of the employee 
     $employeeRepository = $this 
      ->getMockBuilder(EntityRepository::class) 
      ->disableOriginalConstructor() 
      ->getMock(); 
     $employeeRepository->expects($this->once()) 
      ->method('find') 
      ->will($this->returnValue($employee)); 

     // Last, mock the EntityManager to return the mock of the repository 
     $entityManager = $this 
      ->getMockBuilder(ObjectManager::class) 
      ->disableOriginalConstructor() 
      ->getMock(); 
     $entityManager->expects($this->once()) 
      ->method('getRepository') 
      ->will($this->returnValue($employeeRepository)); 

     $salaryCalculator = new SalaryCalculator($entityManager); 
     $this->assertEquals(2100, $salaryCalculator->calculateTotalSalary(1)); 
    } 
} 

Ninguna base de datos de prueba requerida para ese tipo de prueba, solamente (dolorosa) burlona.

Como es importante probar la lógica comercial, no la capa de persistencia.

Sólo para prueba funcional que tiene sentido tener su propia base de datos de prueba que se debe construir y derribar después, y la gran pregunta debe ser:

Cuando hacer pruebas funcionales sentido?

Yo solía pensar que prueba todas las cosas es la respuesta correcta; sin embargo, después de trabajar con un montón de software heredado que en sí mismo apenas fue desarrollado, me volví un poco más perezoso pragmático y considero que ciertas funcionalidades funcionan hasta que se demuestre lo contrario por un error.

Supongamos que tengo una aplicación que analiza un XML, crea un objeto y almacena esos objetos en una base de datos. Si se sabe que funciona la lógica que almacena los objetos en la base de datos (como en: la empresa requiere los datos y, hasta el momento, no está rota), e incluso si esa lógica es una gran pila de basura, no hay inminente necesito probar eso. Como todo lo que necesito para asegurarme de que mi analizador XML extraiga los datos correctos. De la experiencia puedo deducir que se almacenarán los datos correctos.

Existen escenarios en los que la prueba funcional es bastante importante, es decir, si se escribiera una tienda en línea. Allí sería crítico para los negocios que los artículos comprados se almacenaran en la base de datos y aquí la prueba funcional con toda la base de datos de la prueba tiene mucho sentido.

0

Puede utilizar esta clase:

<?php 

namespace Project\Bundle\Tests; 

require_once dirname(__DIR__).'/../../../app/AppKernel.php'; 

use Doctrine\ORM\Tools\SchemaTool; 

abstract class TestCase extends \PHPUnit_Framework_TestCase 
{ 
/** 
* @var Symfony\Component\HttpKernel\AppKernel 
*/ 
protected $kernel; 

/** 
* @var Doctrine\ORM\EntityManager 
*/ 
protected $entityManager; 

/** 
* @var Symfony\Component\DependencyInjection\Container 
*/ 
protected $container; 


public function setUp() 
{ 
    // Boot the AppKernel in the test environment and with the debug. 
    $this->kernel = new \AppKernel('test', true); 
    $this->kernel->boot(); 

    // Store the container and the entity manager in test case properties 
    $this->container = $this->kernel->getContainer(); 
    $this->entityManager = $this->container->get('doctrine')->getEntityManager(); 

    // Build the schema for sqlite 
    $this->generateSchema(); 


    parent::setUp(); 
} 

public function tearDown() 
{ 
    // Shutdown the kernel. 
    $this->kernel->shutdown(); 

    parent::tearDown(); 
} 

protected function generateSchema() 
{ 
    // Get the metadatas of the application to create the schema. 
    $metadatas = $this->getMetadatas(); 

    if (! empty($metadatas)) { 
     // Create SchemaTool 
     $tool = new SchemaTool($this->entityManager); 
     $tool->createSchema($metadatas); 
    } else { 
     throw new Doctrine\DBAL\Schema\SchemaException('No Metadata Classes to process.'); 
    } 
} 

/** 
* Overwrite this method to get specific metadatas. 
* 
* @return Array 
*/ 
protected function getMetadatas() 
{ 
    return $this->entityManager->getMetadataFactory()->getAllMetadata(); 
} 
} 

Y entonces usted puede probar su entidad. Algo así (suponiendo que tiene una entidad de usuario)

//Entity Test 
class EntityTest extends TestCase { 

    protected $user; 

    public function setUp() 
    { 
     parent::setUp(); 
     $this->user = new User(); 
     $this->user->setUsername('username'); 
     $this->user->setPassword('p4ssw0rd'); 


     $this->entityManager->persist($this->user); 
     $this->entityManager->flush(); 

    } 

    public function testUser(){ 

     $this->assertEquals($this->user->getUserName(), "username"); 
     ... 

    } 

} 

Espero que esta ayuda.

Fuente: theodo.fr/blog/2011/09/symfony2-unit-database-tests~~V~~singular~~3rd

+5

Por qué no le das la referencia http://www.theodo.fr/blog/2011/09/symfony2-unit-database-tests/ –

+0

Disculpa. – Munir

34

que nunca han utilizado el PHPUnit_Extensions_Database_TestCase, sobre todo porque por estas dos razones:

  • doesn' t escala bien Si configura y desmonta la base de datos para cada prueba y tiene una aplicación que depende en gran medida de la base de datos, termina creando y soltando el mismo esquema una y otra vez.
  • Me gusta tener mis accesorios no solo dentro de mis pruebas sino también dentro de mi base de datos de desarrollo y algunos accesorios son incluso necesarios para la producción (usuario administrador inicial o categorías de productos o lo que sea). Tenerlos dentro de un xml que solo se puede usar para phpunit no me parece correcto.

Mi camino en la teoría ...

uso el doctrine/doctrine-fixtures-bundle para los accesorios (no importa con qué propósito) y configurar toda la base de datos con todos los accesorios. Luego ejecuto todas las pruebas en esta base de datos y me aseguro de volver a crear la base de datos si una prueba la cambió.

Las ventajas son que no necesito configurar una base de datos nuevamente si una prueba solo se lee pero no cambia nada. Para los cambios, tengo que soltarlo y volver a crearlo o asegurarme de revertir los cambios.

Utilizo sqlite para probarlo porque puedo configurar la base de datos, luego copiar el archivo sqlite y reemplazarlo por uno limpio para recuperar la base de datos original. De esa forma no tengo que soltar la base de datos, crearla y cargar todos los dispositivos de nuevo para una base de datos limpia.

... y en el código

escribí un article about how I do database tests with symfony2 and phpunit.

Aunque utiliza sqlite, creo que uno puede hacer fácilmente los cambios para usar MySQL o Postgres o lo que sea.

Pensando más

Aquí están algunas otras ideas que podrían funcionar:

  • Una vez leí acerca de una configuración de prueba, donde antes de utilizar la base de datos se inicia una transacción (en el método de configuración) y luego usa el tearDown para revertir. De esta forma, no necesita configurar la base de datos nuevamente y solo necesita inicializarla una vez.
  • Mi configuración descrita anteriormente tiene el inconveniente de que la base de datos se configura cada vez que se ejecuta phpunit, incluso si solo ejecuta algunas pruebas de unidad sin interacción con la base de datos. Estoy experimentando con una configuración en la que utilizo una variable global que indica si la base de datos se configuró y luego dentro de las pruebas llamamos a un método que verifica esta variable e inicializa la base de datos si aún no sucedió. De esa manera, solo cuando una prueba necesita la base de datos, la configuración se llevaría a cabo.
  • Un problema con sqlite es que no funciona igual que MySQL en algunos casos raros. Tuve un problema una vez que algo se comportaba de manera diferente en MySQL y sqlite, lo que causaba que fallara una prueba cuando funcionaba con MySQL. No puedo recordar qué era exactamente.
+7

Hay un paquete que proporciona una mayor integración entre los dispositivos de PHPUnit y Doctrine llamados [Liip/FunctionalTestBundle] (https://github.com/liip/LiipFunctionalTestBundle). Usamos esto en combinación con una base de datos sqlite definida en nuestra configuración config_test.yml y ¡ayuda a simplificar mucho las cosas! Una característica realmente útil del paquete es que puede almacenar en caché su base de datos sqlite para que la base de datos no se comparta entre las pruebas con gastos generales reducidos involucrados en la creación y configuración de la base de datos para cada prueba. –

+0

En cuanto a las diferencias entre SQLite y MySQL, creo que las primeras cosas que se topan son funciones de fecha y hora como NOW() que no funcionan en SQLite. Tengo una publicación reciente en mi blog sobre el uso de SQLite para pruebas unitarias que describe algunos de estos problemas http://cvuorinen.net/2012/10/model-testing-using-sqlite-in-memory-database-with-zend- framework/ – Cvuorinen

+0

[Aquí hay] (http://php-and-symfony.matthiasnoback.nl/2011/10/symfony2-use-a-bootstrap-file-for-your-phpunit-tests-and-run-some-console -commands /) otro buen artículo que muestra cómo trabajar con Doctrine y Symfony para probar – naitsirch

Cuestiones relacionadas