11

A causa delle potenziali differenze tra Linq-to-Entities (EF4) e Linq-to-Objects, è necessario utilizzare un database effettivo per verificare che le mie classi di query recuperino i dati da EF correttamente. Sql CE 4 sembra essere lo strumento perfetto per questo, ma mi sono imbattuto in qualche singhiozzo. Questi test utilizzano MsTest.Come utilizzare i database Sql CE 4 per i test funzionali

Il problema che ho è se il database non viene ricreato (a causa di modifiche al modello), i dati continuano ad essere aggiunti al database dopo ogni test senza nulla togliere i dati. Ciò può potenzialmente causare conflitti nei test, con più dati restituiti da query del previsto.

La mia prima idea era di inizializzare un TransactionScope nel metodo TestInitialize e smaltire la transazione in TestCleanup. Sfortunatamente, Sql CE4 non supporta le transazioni.

La mia prossima idea era quella di eliminare il database in TestCleanup tramite una chiamata File.Delete(). Sfortunatamente, questo sembra non funzionare dopo l'esecuzione del primo test, poiché il primo test TestCleanup sembra cancellare il database, ma ogni test dopo il primo non sembra ricreare il database e quindi dà un errore al database il file non è stato trovato

Ho tentato di cambiare TestInitialize e TestCleanup tag per ClassInitialize e ClassCleanup per la mia classe di test, ma che con errori con un NullReferenceException causa del test di corrente prima di ClassInitialize (o almeno così sembra. ClassInitialize è nella classe base in modo forse è causandolo).

Ho esaurito i modi per utilizzare efficacemente Sql CE4 per il test. Qualcuno ha qualche idea migliore?


Modifica: Ho finito per capire una soluzione. Nella mia classe di base di test dell'unità EF, avvio una nuova istanza del mio contesto dati e quindi chiamo context.Database.Delete() e context.Database.Create(). Le unit test eseguiti un po 'più lento, ma ora posso unit test in modo efficace utilizzando un vero e proprio database di


Edit finale: Dopo alcune e-mail avanti e indietro con Microsoft, si scopre che TransactionScope s sono ora autorizzati a SQLCE con l'ultima versione di SqlCE. Tuttavia, se si utilizza EF4 ci sono alcune limitazioni nel fatto che è necessario aprire esplicitamente la connessione al database prima di iniziare la transazione. Il codice seguente mostra un esempio su come utilizzare correttamente SQL CE per l'unità/test funzionali:

[TestMethod] 
    public void My_SqlCeScenario() 
    { 
     using (var context = new MySQLCeModelContext()) //ß derived from DbContext 
     { 
      ObjectContext objctx = ((IObjectContextAdapter)context).ObjectContext; 
      objctx.Connection.Open(); //ß Open your connection explicitly 
      using (TransactionScope tx = new TransactionScope()) 
      { 

       var product = new Product() { Name = "Vegemite" }; 
       context.Products.Add(product); 
       context.SaveChanges(); 
      } 
      objctx.Connection.Close(); //ß close it when done! 
     } 
    } 
+0

Ovviamente SQL CE supporta le transazioni ... ma l'uso di TransactionScope è un modo molto sbagliato di farlo. Fallo normalmente tramite l'oggetto Connection. – leppie

+0

Non sono sicuro di come con entità EF4 senza 'TransactionScope', a meno che non si intenda non chiamare' SaveChanges() ', il che significa che i test non sono test validi. – KallDrexx

+0

Puoi fornire un esempio di come seminare i dati con Sql CE? Io uso EF6 e vorrei testarlo usando sql ce –

risposta

4

Nella tua TestInitialize si dovrebbe effettuare le seguenti operazioni:

System.Data.Entity.Database.DbDatabase.SetInitializer<YourEntityFrameworkClass>(
    new System.Data.Entity.Database.DropCreateDatabaseAlways<YourEntityFrameworkClass>()); 

Questo farà sì che Entity Framework per ricreare sempre il database ogni volta che viene eseguito il test.

Per inciso è possibile creare una classe alternativa che eredita da DropCreateDatabaseAlways. Ciò ti consentirà di seminare il tuo database con i dati impostati ogni volta.

public class DataContextInitializer : DropCreateDatabaseAlways<YourEntityFrameworkClass> { 
    protected override void Seed(DataContext context) { 
     context.Users.Add(new User() { Name = "Test User 1", Email = "[email protected]" }); 
     context.SaveChanges(); 
    } 
} 

Poi, nel tuo inizializzazione si cambierebbe la chiamata a:

System.Data.Entity.Database.DbDatabase.SetInitializer<YourEntityFrameworkClass>(
    new DataContextInitializer()); 
+0

Dopo aver finalmente avuto la possibilità di verificare questo, questo non sembra funzionare. Sembra che il database venga rilasciato prima dell'esecuzione di qualsiasi test, ma non è possibile eliminare il database prima di eseguire singolarmente ciascun test. Ciò sta causando la persistenza dei dati tra i test unitari, causando il fallimento di alcuni altri test. Posso aggirarlo per ora, ma preferirei che funzionasse senza quello. – KallDrexx

+0

Nevermind - è troppo complicato e non si presta bene ai test di unità ... In pratica lo uso per mappare più tabelle a una singola entità e viene eseguito in un nuovo assembly ogni volta che lo chiamo per quella tabella specifica ... difficile da spiegare davvero :) – Buildstarted

+0

Non potresti semplicemente cancellare le tabelle dopo ogni test e riutilizzarle con il metodo seed? O ogni test è un diverso insieme di tabelle e cosa no? – Buildstarted

3

Ho trovato l'approccio nel "montaggio finale" funziona anche per me. Tuttavia, è DAVVERO fastidioso. Non è solo per test, ma ogni volta che si desidera utilizzare TransactionScope con Entity Framework e SQL CE. Voglio fare il codice una sola volta e avere il mio supporto dell'app sia SQL Server che SQL CE, ma ovunque io usi le transazioni devo farlo. Sicuramente il team di Entity Framework avrebbe dovuto gestire questo per noi!

Nel frattempo, ho fatto un passo in più per renderlo un po 'più pulito nel mio codice. Aggiungere questo blocco al contesto dati (qualsiasi classe si deriva da DbContext):

public MyDataContext() 
{ 
    this.Connection.Open(); 
} 

protected override void Dispose(bool disposing) 
{ 
    if (this.Connection.State == ConnectionState.Open) 
     this.Connection.Close(); 

    base.Dispose(disposing); 
} 

private DbConnection Connection 
{ 
    get 
    { 
     var objectContextAdapter = (IObjectContextAdapter) this; 
     return objectContextAdapter.ObjectContext.Connection; 
    } 
} 

Questo rende molto più pulito quando in realtà utilizza:

using (var db = new MyDataContext()) 
{ 
    using (var ts = new TransactionScope()) 
    { 
     // whatever you need to do 

     db.SaveChanges(); 
     ts.Complete(); 
    } 
} 

Anche se suppongo che se si progetta il tuo app in modo tale che tutte le modifiche vengano eseguite in un'unica chiamata a SaveChanges(), quindi la transazione implicita sarebbe sufficiente. Per lo scenario di test, vogliamo eseguire il rollback di tutto invece di chiamare ts.Complete(), quindi è sicuramente necessario lì. Sono sicuro che ci sono altri scenari in cui abbiamo bisogno dell'ambito della transazione disponibile. È un peccato non essere supportato direttamente da EF/SQLCE.

+0

soluzione interessante. Ho appena fatto una soluzione in cui la mia classe 'IUnitOfWork' ha metodi' BeginTransaction() 'e' EndTransaction (bool commit) '. 'BeginTransaction' aprirà la connessione se ancora non esiste, e' EndTransaction' mi permetterà di completare o eseguire il rollback della transazione facilmente. Finora questo sembra funzionare altrettanto bene. Spero che in futuro potrò semplicemente usare gli ambiti di transazione da soli. – KallDrexx

Problemi correlati