2010-04-21 19 views
9

Estoy utilizando HSQLDB para pruebas de integración de capa de datos, lo cual es genial. Sin embargo, estoy descubriendo que mis restricciones de clave externa están obstaculizando mis pruebas. Por ejemplo, para probar una selección simple en una tabla, tengo que insertar datos ficticios en cinco tablas adicionales. Esto me hace querer tirar cosas.TDD con HSQLDB - eliminación de claves externas

Tengo anotaciones JPA en todo el código del modelo y he configurado Hibernate para recrear el esquema (hbm2ddl.create-drop) en la configuración. Las uniones se interpretan correctamente como restricciones de clave externa cuando se generan las tablas.

Lo que me gustaría es que ya sea:

  1. no crea las claves externas inicialmente (ideal, más limpio), o
  2. encontrar una manera de soltar mediante programación todas las claves externas en la base de datos (un poco hacky pero hará el trabajo)

Si es útil, estoy usando Spring para autoconectar estas pruebas. Las pruebas en cuestión heredan de AbstractTransactionalJUnit4SpringContextTests.

¿Qué opinas? Se puede hacer esto?

Respuesta

8

Puede desactivar las restricciones FK con la siguiente instrucción:

SET REFERENTIAL_INTEGRITY FALSE; 

Usted puede ejecutarlo a través de un JDBC Statement antes de que sus métodos de prueba (y fijarlo de nuevo a TRUE después).

+1

¡Gracias! Simplemente cortó por sí solo mi clase de prueba a la mitad. Curiosamente, esto también hace que sea más fácil desentrañar donde Hibernate está causando uniones internas innecesarias. – roufamatic

+0

Para cualquiera que use AbstractTransactionalJUnit4SpringContextTests ... la magia es esta: 'simpleJdbcTemplate.getJdbcOperations(). Execute (" SET REFERENTIAL_INTEGRITY FALSE; ");' – roufamatic

+0

@roufamatic Me alegra que lo encuentre útil. –

0

Consideraría dedicar algún tiempo a crear un par de accesorios, posiblemente con DBUnit, que inserte @Before.

Por cierto, AbstractTransactionalJUnit4Test está en desuso en Spring 3.0

+0

re: obsoleto ... Quise decir "AbstractTransactionalJUnit4SpringContextTests" que corregí anteriormente. FWIW estamos usando Spring 2.5. – roufamatic

8

Me encontré con exactamente el mismo problema al intentar probar mi DAO con el conjunto de datos xml plano. Config es DBUnit + HSQLDB 2.2.8 + + junit4 primavera + JPA-> todos juntos conduce a

java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: foreign key no parent; FK13EE6CE6F09A6AAC table: **** 

me encontré con un buen solución mediante la implementación de un oyente que se extiende AbstractTestExecutionListener. Debería especificar el tipo de acción que se tomará antes de cada prueba, en nuestro caso, deshabilitar las restricciones de clave externa. NOTA: la sintaxis puede variar según la versión de HSQLDB utilizada.

public class ForeignKeyDisabling extends AbstractTestExecutionListener {  
    @Override 
    public void beforeTestClass(TestContext testContext) throws Exception { 
     IDatabaseConnection dbConn = new DatabaseDataSourceConnection(
       testContext.getApplicationContext().getBean(DataSource.class) 
       ); 
     dbConn.getConnection().prepareStatement("SET DATABASE REFERENTIAL INTEGRITY FALSE").execute(); 

    } 
} 

A continuación, sólo tiene que añadir este oyente en la colección que ya existen en sus pruebas:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration({"applicationContext-test.xml"}) 
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DataSetTestExecutionListener.class, ForeignKeyDisabling.class}) 
+0

Esa es una manera elegante de hacerlo. – roufamatic

+1

¿no debería entonces volver a establecerlo al principio de cada prueba (después de que se haya configurado el db) con "SET DATABASE REFERENCIAL INTEGRITY TRUE"? – wannabeartist

+0

Establecer dicho código en setUp también podría satisfacer sus necesidades. En mi caso, el conjunto de pruebas completo dependía de esta modificación de estructura. Pero el resultado debería ser el mismo. –

1

Base en la inspiración en este hilo, he creado solución algo más robusto para este problema. El punto era que realmente me gustan las restricciones al ejecutar la prueba y todas las demás soluciones simplemente lo mantuvieron inhabilitado. Este código los desactivará solo por la duración de la importación del conjunto de datos y luego los volverá a habilitar. Y se puede extender fácilmente para admitir otro motor de base de datos:

import com.github.springtestdbunit.DbUnitTestExecutionListener; 
import org.apache.log4j.Logger; 
import org.dbunit.database.DatabaseDataSourceConnection; 
import org.dbunit.database.IDatabaseConnection; 
import org.springframework.test.context.TestContext; 

import javax.sql.DataSource; 
import java.sql.Connection; 
import java.sql.SQLException; 

/** 
* Class DisableForeignKeysDbUnitTestExecutionListener 
* Simple wrapper class around DbUnitTestExecutionListener, which - for the time of importing the database - 
* disables Foreign Key Constraints checks. 
* This class can be extended by simply overriding toggleForeignKeysConstraintsForDbEngine(Connection, String, boolean); 
* subclasses should always call super-implementation for default case. 
*/ 
public class DisableForeignKeysDbUnitTestExecutionListener 
    extends DbUnitTestExecutionListener 
{ 
    private static final Logger logger = Logger.getLogger(DisableForeignKeysDbUnitTestExecutionListener.class); 
    private Connection cachedDbConnection; 

    @Override 
    public void beforeTestMethod(TestContext testContext) 
     throws Exception 
    { 
     this.toggleForeignKeysConstraints(testContext, false); 
     super.beforeTestMethod(testContext); 
     this.toggleForeignKeysConstraints(testContext, true); 
    } 

    /** 
    * Method should perform query to disable foreign keys constraints or return false, 
    * if it is not able to perform such query (e.g. unknown database engine) 
    * 
    * @param connection Database connection 
    * @param dbProductName Name of the database product (as reported by connection metadata) 
    * @param enabled  Expected state of foreign keys after the call 
    * 
    * @return True, if there was suitable statement for specified engine, otherwise false 
    * 
    * @throws SQLException 
    */ 
    protected boolean toggleForeignKeysConstraintsForDbEngine(Connection connection, String dbProductName, boolean enabled) 
     throws SQLException 
    { 
     switch (dbProductName) 
     { 
      case "HSQL Database Engine": 
       connection.prepareStatement("SET DATABASE REFERENTIAL INTEGRITY " + (enabled ? "TRUE" : "FALSE")) 
          .execute(); 
       return (true); 
     } 
     return (false); 
    } 

    private void toggleForeignKeysConstraints(TestContext testContext, boolean enabled) 
    { 
     try 
     { 
      Connection connection = this.getDatabaseConnection(testContext); 
      String databaseProductName = connection.getMetaData().getDatabaseProductName(); 
      if (!this.toggleForeignKeysConstraintsForDbEngine(connection, databaseProductName, enabled)) 
      { 
       throw new IllegalStateException("Unknown database engine '" + databaseProductName + 
                "'. Unable to toggle foreign keys constraints."); 
      } 
     } 
     catch (Throwable throwable) 
     { 
      logger.error("Unable to toggle Foreign keys constraints: " + throwable.getLocalizedMessage()); 
     } 
    } 

    synchronized private Connection getDatabaseConnection(TestContext testContext) 
     throws SQLException 
    { 
     if (this.cachedDbConnection == null) 
     { 
      DataSource dataSource = testContext.getApplicationContext().getBean(DataSource.class); 
      if (dataSource == null) 
      { 
       throw new IllegalStateException("Unable to obtain DataSource from ApplicationContext. " + 
                "Foreign constraints will not be disabled."); 
      } 

      IDatabaseConnection dsConnection = new DatabaseDataSourceConnection(dataSource); 
      this.cachedDbConnection = dsConnection.getConnection(); 
     } 

     return (this.cachedDbConnection); 
    } 
} 
Cuestiones relacionadas