Unità di test DbContext

Ho ricercato alcune informazioni sulle tecniche che potrei usare per testare un DbContext . Vorrei aggiungere alcuni dati in memoria al contesto in modo che i miei test possano essere eseguiti contro di esso. Sto usando l’approccio Database-First.

I due articoli che ho trovato più utili erano questo e questo . Questo approccio si basa sulla creazione di un’interfaccia IContext che sia MyContext che FakeContext implementeranno, consentendo di simulare il contesto.

Tuttavia, sto cercando di evitare l’uso di repository per l’EF astratto, come indicato da alcune persone, poiché EF 4.1 implementa già repository e unità di pattern di lavoro attraverso DbSet e DbContext e vorrei davvero conservare tutte le funzionalità implementate da EF Team senza doverli mantenere personalmente con un repository generico, come già facevo in un altro progetto (ed era piuttosto doloroso).

Lavorare con un IContext mi porterà allo stesso percorso (o no?).

Ho pensato di creare un FakeContext che erediti dal MyContext principale e quindi di sfruttare il DbContext sottostante per eseguire i miei test senza colpire il database. Non sono riuscito a trovare implementazioni simili, quindi spero che qualcuno possa aiutarmi in questo.

    Sto facendo qualcosa di sbagliato, o questo potrebbe portarmi ad alcuni problemi che non sto anticipando?

    Porsi una sola domanda: cosa testerai?

    Hai menzionato FakeContext e Mocking del contesto: perché usarli entrambi? Questi sono solo diversi modi per fare lo stesso: fornire solo l’implementazione del contesto.

    C’è ancora un problema più grande: il contesto o il set di falsi o derisioni ha solo un risultato: non stai più testando il tuo codice reale.

    Semplice esempio:

     public interface IContext : IDisposable { IDbSet MyEntities { get; } } public class MyEntity { public int Id { get; set; } public string Path { get; set; } } public class MyService { private bool MyVerySpecialNetMethod(e) { return File.Exists(e.Path); } public IEnumerable GetMyEntities() { using (IContext context = CreateContext()) { return context.MyEntities .Where(e => MyVerySpecialNetMethod(e)) .Select(e) .ToList(); } } } 

    Ora immagina di avere questo nel tuo SUT (sistema sotto test – in caso di unit test è un’unità = di solito un metodo). Nel codice di test fornisci FakeContext e FakeSet e funzionerà: avrai un test verde. Ora nel codice di produzione DbContext un altro derivato DbContext e DbSet e otterrai un’eccezione in fase di runtime.

    Perché? Perché utilizzando FakeContext hai anche cambiato il provider LINQ e invece LINQ alle Entità su cui stai eseguendo LINQ su Oggetti, quindi chiamando i metodi .NET locali che non possono essere convertiti in SQL funziona così come molte altre funzionalità LINQ che non sono disponibili in LINQ alle Entità ! Ci sono anche altri problemi che puoi trovare con la modifica dei dati: integrità referenziale, eliminazioni a cascata, ecc. Questo è il motivo per cui ritengo che il codice relativo al contesto / LINQ alle quadro debba essere coperto con test di integrazione ed eseguito sul database reale.

    A partire da EF 4.3, è ansible testare il codice dell’unità iniettando un falso DefaultConnectionFactory prima di creare il contesto.

    Sto sviluppando una libreria open-source per risolvere questo problema.

    http://effort.codeplex.com

    Un piccolo teaser:

    Non è necessario aggiungere alcun codice boilerplate, basta semplicemente chiamare l’API appropriata della libreria, ad esempio:

     var context = Effort.ObjectContextFactory.CreateTransient(); 

    All’inizio questo potrebbe sembrare magico, ma l’object ObjectContext creato comunicherà con un database in memoria e non parlerà affatto con il vero database originale. Il termine “transitorio” si riferisce al ciclo di vita di questo database, vive solo durante la presenza dell’object ObjectContext creato. Gli oggetti ObjectContext creati simultaneamente comunicano con istanze di database dedicate, i dati non vengono condivisi tra loro. Ciò consente di scrivere facilmente test automatici.

    La libreria offre varie funzionalità per personalizzare la creazione: condividere i dati tra le istanze, impostare i dati iniziali del database, creare database falsi su diversi livelli di dati … controllare il sito del progetto per maggiori informazioni.

    Entity Framework 4.1 è vicino a essere messo alla prova nei test, ma richiede un piccolo sforzo in più. Il modello T4 fornisce una class derivata DbContext che contiene le proprietà DbSet. Le due cose che penso tu abbia bisogno di prendere in giro sono gli oggetti DbSet che queste proprietà restituiscono e le proprietà e i metodi che stai utilizzando nella class derivata DbContext. Entrambi possono essere ottenuti modificando il modello T4.

    Brent McKendrick ha mostrato i tipi di modifiche che devono essere apportate in questo post , ma non le modifiche al modello T4 che possono raggiungere questo objective. Approssimativamente, questi sono:

    1. Convertire le proprietà DbSet nella class derivata DbContext nelle proprietà IDbSet.
    2. Aggiungi una sezione che genera un’interfaccia per la class derivata DbContext che contiene le proprietà IDbSet e qualsiasi altro metodo (come SaveChanges) di cui avrai bisogno per prendere in giro.
    3. Implementare la nuova interfaccia nella class derivata DbContext.

    Per chiunque sia ancora alla ricerca di risposte – ho scritto una semplice libreria per facilitare il beffeggiamento di DbContext in un modo molto semplice. Per i dettagli vedere la mia altra risposta su SO a una domanda simile: https://stackoverflow.com/a/33716219/111438 .

    PS – Sono d’accordo con ciò che sta dicendo Ladislav Mrnka, tuttavia penso che in alcuni casi sia abbastanza inevitabile dover prendere in giro il tuo DbSet ed eseguire test unitari contro di esso. Anche se è necessario tenere presente che non si dovrebbero testare le query LINQ per verificare se restituiscono dati corretti. Ecco dove i test di integrazione sono più applicabili.