2015-06-26 7 views
6

In C#/nHibernate-projects Sto usando SQLite per testare il mio codice, utilizzando approssimativamente il metodo descritto qui: http://ayende.com/blog/3983/nhibernate-unit-testing.Copia il database SQLite in memoria per rendere più veloci i test di unità

Tuttavia, trovo che la creazione e la configurazione del database in memoria richiede in genere circa 150 ms. Ho un sacco di test unitario quindi questo si aggiunge rapidamente.

Voglio costruire e configurare il database una volta, memorizzarlo in una variabile statica e copiarlo ogni volta che un test di unità richiede un database.

Come eseguire il backup di un database in memoria?

Prima ho provato a creare un database in memoria denominato. Secondo https://www.sqlite.org/inmemorydb.html questo è possibile. Ho usato per avere:

private const string ConnectionString = "Data Source=:memory:;Version=3;"; 

stringhe di connessione che ho provato sono:

private const string ConnectionString = "FullUri=file:memorydb.db?mode=memory&cache=shared"; 
    private const string ConnectionString2 = "FullUri=file:memorydb2.db?mode=memory&cache=shared"; 

Così ora non mi resta che scoprire come copiare rapidamente i contenuti da uno all'altro? Sono quasi arrivato: posso creare due database in memoria e chiamare "BackupDatabase" per copiare il database.

Il test di unità, tuttavia, si comporta come se il database di "istanza" non avesse tabelle, anche il database "prototipo".

 private static ISessionFactory _prototypeSessionFactory; 
     private const string InstanceConnectionString = "FullUri=file:memorydb.db?mode=memory&cache=shared"; 

     private const string PrototypeConnectionString = "FullUri=file:memorydb2.db?mode=memory&cache=shared"; 
     private SQLiteConnection _instanceConnection; 
     private ISessionFactory _instanceSessionFactory; 

     public DatabaseScope(Assembly assembly) 
     { 
      var prototyeConfiguration = SQLiteConfiguration.Standard.ConnectionString(PrototypeConnectionString); 
      var cfg = Fluently 
       .Configure() 
       .Database(prototyeConfiguration) 
       .Mappings(m => m.HbmMappings.AddFromAssembly(assembly)); 
      cfg.ExposeConfiguration(BuildSchema); 
      _prototypeSessionFactory = cfg.BuildSessionFactory(); 

      var instanceConfiguration = SQLiteConfiguration.Standard.ConnectionString(InstanceConnectionString); 
      _instanceSessionFactory = Fluently 
       .Configure() 
       .Database(instanceConfiguration) 
       .BuildSessionFactory(); 

      CopyDatabase(); 
     } 

     private void CopyDatabase() 
     { 
      var cnnIn = new SQLiteConnection(PrototypeConnectionString); 
      var cnnOut = new SQLiteConnection(InstanceConnectionString); 
      cnnIn.Open(); 
      cnnOut.Open(); 
      cnnIn.BackupDatabase(cnnOut, "main", "main", -1, null, -1); 
      cnnIn.Close(); 
      cnnOut.Close(); 
     } 
+0

secondo [questo articolo] (https://www.sqlite.org/backup.html) (vedi loadOrSaveDb esempio). Sembra che tu debba usare 'sqlite3_backup_init()' con un puntatore al DB di destinazione. Comunque non penso che risolverà il tuo problema. i tuoi test suonano come un test di integrazione non UT. Penso che dovresti prendere in considerazione l'uso di un framework di simulazione e sostituire il tuo DB. –

+0

Grazie per la tua risposta, Old Fox. Hai ragione: sono in parte test di integrazione. E funzionano perfettamente, solo un po 'lento, che fa male per più di 300 test. Non vedo l'ora di riscrivere molti test, ma per amor di discussione: come procederesti a sostituire il database usando un sistema di simulazione? (Considero quello che sto facendo beffardo: sto usando SQLite per deridere il mio database, assicurandomi che tutto funzioni anche quando i miei mapping nHibernate sono corretti.) – realbart

+0

Penso che 300+ test di integrazione che verificano la mappatura è un po ' troppo ... Credo che il tuo codice funzioni direttamente contro 'NHibernate'. Se è così, ti consiglio di dividere i test in 2 tipi: 1. comportamento + chiamare l'OEM correttamente (OEM falso probabilmente la maggior parte dei test esistenti). 2. Test di integrazione - OEM ha mappato correttamente il tipo (probabilmente il 10% rispetto ai 300+ test). Informazioni su come simulare il DB, non pensarci più, è stata colpa mia ... –

risposta

2

ho finito con questo codice funzionante. La durata del test dell'unità è passata da oltre dieci minuti a meno di due minuti. (Codice leggermente semplificata per migliorare la leggibilità)

using System; 
using System.Data; 
using System.Data.SQLite; 
using System.IO; 
using System.Reflection; 
using FluentNHibernate.Cfg; 
using FluentNHibernate.Cfg.Db; 
using NHibernate; 
using NHibernate.Mapping; 
using NHibernate.Tool.hbm2ddl; 

namespace TestHelper.DbHelper.SqLite 
{ 
    public class DatabaseScope : IDisposable 
    { 
     private static Assembly _prototypeAssembly; 
     private const string PrototypeConnectionString = "FullUri=file:prototype.db?mode=memory&cache=shared"; 
     private static ISessionFactory _prototypeSessionFactory; 
     private static SQLiteConnection _prototypeConnection; 

     private const string InstanceConnectionString = "FullUri=file:instance.db?mode=memory&cache=shared"; 
     private ISessionFactory _instanceSessionFactory; 
     private SQLiteConnection _instanceConnection; 

     public DatabaseScope(Assembly assembly) 
     { 
      InitDatabasePrototype(assembly); 
      InitDatabaseInstance(); 
     } 

     private void InitDatabasePrototype(Assembly assembly) 
     { 
      if (_prototypeAssembly == assembly) return; 

      if (_prototypeConnection != null) 
      { 
       _prototypeConnection.Close(); 
       _prototypeConnection.Dispose(); 
       _prototypeSessionFactory.Dispose(); 
      } 

      _prototypeAssembly = assembly; 

      _prototypeConnection = new SQLiteConnection(PrototypeConnectionString); 
      _prototypeConnection.Open(); 

      _prototypeSessionFactory = Fluently 
       .Configure() 
       .Database(SQLiteConfiguration.Standard.ConnectionString(PrototypeConnectionString)) 
       .Mappings(m => m.HbmMappings.AddFromAssembly(assembly)) 
       .ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(false, true, false, _prototypeConnection, null)) 
       .BuildSessionFactory(); 
     } 

     private void InitDatabaseInstance() 
     { 
      _instanceSessionFactory = Fluently 
       .Configure() 
       .Database(SQLiteConfiguration.Standard.ConnectionString(InstanceConnectionString)) 
       .Mappings(m => m.HbmMappings.AddFromAssembly(_prototypeAssembly)) 
       .BuildSessionFactory(); 

      _instanceConnection = new SQLiteConnection(InstanceConnectionString); 
      _instanceConnection.Open(); 

      _prototypeConnection.BackupDatabase(_instanceConnection, "main", "main", -1, null, -1); 
     } 

     public ISession OpenSession() 
     { 
      return _instanceSessionFactory.OpenSession(_instanceConnection); 
     } 

     public void Dispose() 
     { 
      _instanceConnection.Close(); 
      _instanceConnection.Dispose(); 
      _instanceSessionFactory.Dispose(); 
     } 
    } 
} 
1

Quello che ho osservato con SQLite in-memory database è che, non appena si chiude il collegamento, tutto nel db è andato. Quindi, per fare quello che vuoi,

  1. Creare fabbrica di sessione per il database di backup, sessione aperta e costruire lo schema non chiudere questa sessione fino al termine della suite di test dell'intero

  2. Creare fabbrica di sessione per la vostra database di destinazione, sessione aperta e utilizzare la connessione da questo oggetto di sessione e la connessione dalla sessione creata dal passaggio 1 per copiare i dati

  3. utilizzare la sessione creata nel passaggio 2 per la prova e chiudere una volta la prova è finita

Un'altra soluzione è quella di utilizzare la singola sessione di eseguire più test (tutti i test in un'unica apparecchiatura di prova), allora non c'è bisogno di creare una nuova fabbrica di sessione per test, ma per TestFixture

+0

Non sta creando lo schema la parte che richiede più tempo della creazione di un database SQLite da Nhibernate-Mappings? Questo è il passo che sto cercando di circomvent ... Non c'è un comando "truncate all tables", c'è? – realbart

+0

Non sono sicuro che la creazione dello schema sia il collo di bottiglia o, creando e stabilendo la connettività. Un'opzione per questo è creare in memoria db una volta e riutilizzare la stessa sessione per più test con la creazione della transazione ad ogni inizio di test e il rollback alla fine del test. –

+0

Jip. L'ho fatto funzionare seguendo le tue istruzioni. Ho fatto riferimento al db sbagliato da qualche altra parte nel codice. – realbart

Problemi correlati