2010-04-21 16 views
9

Sto utilizzando HSQLDB per i test di integrazione del livello dati, il che è ottimo. Tuttavia, sto scoprendo che i miei limiti alle chiavi esterne stanno ostacolando i miei test. Ad esempio, per testare una selezione semplice su una tabella, devo inserire i dati fittizi in cinque tabelle aggiuntive. Questo mi fa venire voglia di buttare le cose.TDD con HSQLDB - rimozione chiavi esterne

Ho annotazioni JPA in tutto il codice del modello e ho configurato Hibernate per ricreare lo schema (hbm2ddl.create-drop) nella configurazione. I join vengono interpretati correttamente come vincoli di chiave esterna quando vengono generate le tabelle.

Quello che mi piacerebbe è a uno:

  1. non crea le chiavi esterne inizialmente (ideale, più pulito), o
  2. trovare un modo per cadere di programmazione tutte le chiavi esterne nel database (kinda hacky ma avrà il lavoro finito)

Se è utile, sto usando Spring per autowire questi test. I test in questione ereditano da AbstractTransactionalJUnit4SpringContextTests.

Cosa ne pensi? Può essere fatto?

risposta

8

È possibile disattivare i vincoli FK con le seguenti istruzioni:

SET REFERENTIAL_INTEGRITY FALSE; 

Si poteva eseguirlo tramite un JDBC Statement prima che i vostri metodi di prova (e impostarlo indietro a TRUE dopo).

+1

Grazie !!! Hai solo tagliato da solo la mia classe di test a metà. È interessante notare che questo rende anche più facile sradicare dove Hibernate sta causando inutili join interni. – roufamatic

+0

Per chiunque utilizzi AbstractTransactionalJUnit4SpringContextTests ... la magia è questa: 'simpleJdbcTemplate.getJdbcOperations(). Execute (" SET REFERENTIAL_INTEGRITY FALSE; ");' – roufamatic

+0

@roufamatic Contento che lo trovi utile. –

0

Vorrei prendere in considerazione la possibilità di dedicare un po 'di tempo alla creazione di un paio di dispositivi, possibilmente con DBUnit, che si inseriscono in @Before.

BTW, AbstractTransactionalJUnit4Test è deprecato in primavera 3.0

+0

re: deprecato ... Intendevo "AbstractTransactionalJUnit4SpringContextTests" che ho corretto sopra. FWIW stiamo usando Spring 2.5. – roufamatic

8

Ho riscontrato lo stesso identico problema durante il tentativo di testare il mio DAO con dataset flat xml. Config è DBUnit + HSQLDB 2.2.8 + JUnit4 + molla + JPA-> tutti insieme che porta a

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

ho trovato una bella soluzione mediante l'attuazione di un ascoltatore che si estende AbstractTestExecutionListener. Dovresti specificare il tipo di azione da intraprendere prima di ogni test, nel nostro caso, disabilitando i vincoli di chiave esterna. NOTA: la sintassi può variare a seconda della versione di HSQLDB utilizzata.

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

    } 
} 

È quindi solo bisogno di aggiungere questo ascoltatore nella collezione già in atto nei test:

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

Questo è un modo elegante per farlo. – roufamatic

+1

non dovresti quindi reimpostarlo all'inizio di ogni test (dopo che il db è stato impostato) con "SET DATABASE REFERENTIAL INTEGRITY TRUE"? – wannabeartist

+0

L'impostazione di tale codice in setUp potrebbe soddisfare anche le tue esigenze. Nel mio caso l'intera suite di test dipendeva da questa modifica della struttura. Ma il risultato dovrebbe essere lo stesso. –

1

Base sul ispirazione in questo thread, ho creato soluzione un po 'più robusta per questo problema. Il punto era, mi piacciono molto i vincoli durante l'esecuzione del test e tutte le altre soluzioni lo hanno semplicemente disabilitato. Questo codice li disabiliterà solo per la durata dell'importazione del set di dati e quindi li riattiverà. E può essere facilmente esteso per supportare un altro motore DB:

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