ASP.NET MVC3 ed Entity Framework Code prima architettura

La mia domanda precedente mi ha fatto ripensare a strati, repository, dipendenze e cose architettoniche come questa.

La mia architettura ora si presenta così:
Sto usando il codice EF prima, quindi ho appena creato le classi POCO e il contesto. Questo crea db e modello.
Livello più alto sono le classi del livello aziendale (Provider). Sto utilizzando provider diversi per ogni dominio … come MemberProvider, RoleProvider, TaskProvider ecc. E sto creando una nuova istanza del mio DbContext in ciascuno di questi provider.
Quindi istanziato questi provider nei miei controller, ottengo i dati e li inviamo a Views.

La mia architettura iniziale comprendeva il repository, di cui mi sono liberato perché mi è stato detto che aggiunge solo complessità, quindi perché non uso solo EF. Volevo farlo … lavorando con EF direttamente dai controller, ma devo scrivere test ed è stato un po ‘complicato con il database reale. Ho dovuto fingere dati falsi in qualche modo. Così ho creato un’interfaccia per ogni provider e ho creato fornitori falsi con dati codificati negli elenchi. E con questo sono tornato a qualcosa, dove non sono sicuro di come procedere correttamente.

Queste cose cominciano a essere complicate troppo rapidamente … molti approcci e “battute d’arresto” … crea troppo rumore e codice inutile.

Esiste un’architettura SEMPLICE e verificabile per la creazione e l’applicazione ASP.NET MVC3 con Entity Framework?

    Se si desidera utilizzare TDD (o qualsiasi altro approccio di test con copertura di prova elevata) ed EF insieme, è necessario scrivere integrazioni o test end-to-end. Il problema qui è che qualsiasi approccio con il mocking di un contesto o di un repository crea semplicemente un test che può testare la logica del livello superiore (che usa quei mock) ma non la tua applicazione.

    Semplice esempio:

    Definiamo un repository generico:

    public interface IGenericRepository { IQueryable GetQuery(); ... } 

    E lascia scrivere alcuni metodi di business:

     public IEnumerable DoSomethingImportant() { var data = MyEntityRepo.GetQuery().Select((e, i) => e); ... } 

    Ora se prendi in giro il repository userai Linq-To-Objects e avrai un test verde, ma se esegui l’applicazione con Linq-To-Entities otterrai un’eccezione perché il sovraccarico di selezione con gli indici non è supportato in L2E.

    Questo è stato un semplice esempio, ma lo stesso può accadere con l’utilizzo di metodi in query e altri errori comuni. Inoltre ciò influisce anche su metodi come Add, Update, Delete solitamente esposti sul repository. Se non scrivi una simulazione che simula esattamente il comportamento del contesto EF e dell’integrità referenziale, non testerai la tua implementazione.

    Un’altra parte della storia sono i problemi con il caricamento Lazy, che può anche essere rilevato con test unitari contro i mock.

    Per questo motivo dovresti anche introdurre test di integrazione o end-to-end che funzioneranno contro database reali usando il contesto EF reale e L2E. Btw. è necessario utilizzare test end-to-end per utilizzare correttamente TDD. Per la scrittura di test end-to-end in ASP.NET MVC è ansible WatiN e possibilmente anche SpecFlow per BDD, ma questo aggiungerà molto lavoro, ma la tua applicazione sarà davvero testata. Se vuoi saperne di più su TDD consiglio questo libro (l’unico svantaggio è che gli esempi sono in Java).

    I test di integrazione hanno senso se non si utilizza un repository generico e si nascondono le query in una class che non espone IQueryable ma restituisce direttamente i dati.

    Esempio:

     public interface IMyEntityRepository { MyEntity GetById(int id); MyEntity GetByName(string name); } 

    Ora puoi semplicemente scrivere test di integrazione per verificare l’implementazione di questo repository perché le query sono nascoste in questa class e non sono esposte ai livelli superiori. Ma questo tipo di repository è in qualche modo considerato come vecchia implementazione utilizzata con stored procedure. Perderai molte funzionalità ORM con questa implementazione o dovrai eseguire molto lavoro aggiuntivo, ad esempio aggiungere un modello di specifica per poter definire una query nel livello superiore.

    In ASP.NET MVC è ansible sostituire parzialmente i test end-to-end con test di integrazione a livello di controller.

    Modifica in base al commento:

    Non dico che siano necessari test unitari, test di integrazione e test end-to-end. Dico che fare applicazioni testate richiede molto più impegno. La quantità e i tipi di test necessari dipendono dalla complessità della tua applicazione, dal futuro previsto dell’applicazione, dalle tue capacità e competenze degli altri membri del team.

    I piccoli progetti possono essere creati senza test (ok, non è una buona idea, ma lo abbiamo fatto tutti e alla fine ha funzionato) ma una volta che un progetto ha superato qualche soglia puoi scoprire che l’introduzione di nuove funzionalità o il mantenimento del progetto è molto difficile perché non si è mai sicuri se si rompe qualcosa che ha già funzionato – si chiama regressione. La migliore difesa contro la regressione è una buona serie di test automatici.

    • I test unitari ti aiutano a testare il metodo. Tali test dovrebbero idealmente coprire tutti i percorsi di esecuzione nel metodo. Questi test dovrebbero essere molto brevi e facili da scrivere: la parte complicata può essere quella di impostare dipendenze (mock, faktes, stub).
    • I test di integrazione consentono di testare le funzionalità su più livelli e di solito su più processi (applicazione, database). Non è necessario averli per tutto, è più sull’esperienza per selezionare dove sono utili.
    • I test end-to-end sono qualcosa di simile alla validazione del caso d’uso / storia / funzione dell’utente. Dovrebbero coprire l’intero stream del requisito.

    Non è necessario testare più volte un feticcio – se si sa che la funzionalità è stata testata in un test end-to-end non è necessario scrivere test di integrazione per lo stesso codice. Inoltre, se si conosce che il metodo ha solo un singolo percorso di esecuzione che è coperto dal test di integrazione, non è necessario scrivere un test unitario per questo. Funziona molto meglio con l’approccio TDD in cui inizi con un test di grandi dimensioni (end-to-end o integrazione) e approfondisci i test unitari.

    A seconda dell’approccio di sviluppo, non è necessario iniziare con più tipi di test dall’inizio, ma è ansible presentarli in un secondo momento, in quanto l’applicazione diventerà più complessa. L’eccezione è TDD / BDD in cui dovresti iniziare a utilizzare almeno test end-to-end e unitari prima ancora di scrivere una singola riga di altro codice.

    Quindi stai facendo la domanda sbagliata. La domanda non è cosa è più semplice? La domanda è: cosa ti aiuterà alla fine e quale complessità si adatta alla tua applicazione? Se si desidera avere un’applicazione e una business logic testate con unità semplice, è necessario includere il codice EF in altre classi che possono essere prese in giro. Ma allo stesso tempo è necessario introdurre altri tipi di test per garantire che il codice EF funzioni.

    Non posso dire quale approccio si adatta al tuo ambiente / progetto / team / ecc. Ma posso spiegare un esempio del mio progetto passato:

    Ho lavorato al progetto per circa 5-6 mesi con due colleghi. Il progetto era basato su ASP.NET MVC 2 + jQuery + EFv4 ed è stato sviluppato in modo incrementale e iterativo. Aveva un sacco di complicate logiche di business e un sacco di complicate query di database. Abbiamo iniziato con repository generici e copertura di codice elevato con test unitari + test di integrazione per convalidare la mapping (test semplici per l’inserimento, l’eliminazione, l’aggiornamento e la selezione dell’ quadro). Dopo pochi mesi abbiamo scoperto che il nostro approccio non funziona. Abbiamo avuto più di 1.200 test unitari, copertura del codice di circa il 60% (che non è molto buona) e un sacco di problemi di regressione. La modifica di qualsiasi cosa nel modello EF potrebbe introdurre problemi imprevisti in parti che non sono state toccate per diverse settimane. Abbiamo scoperto che mancano test di integrazione o test end-to-end per la nostra logica applicativa. La stessa conclusione è stata fatta su un team parallelo che ha lavorato su un altro progetto e l’utilizzo di test di integrazione è stato considerato come una raccomandazione per nuovi progetti.

    L’utilizzo del modello di repository aggiunge complessità? Nel tuo scenario, io non la penso così. Rende il TDD più facile e il tuo codice più gestibile. Prova ad utilizzare un modello di repository generico per una maggiore separazione e un codice più pulito.

    Se vuoi saperne di più su TDD e design pattern in Entity Framework, dai un’occhiata a: http://msdn.microsoft.com/en-us/ff714955.aspx

    Tuttavia sembra che tu stia cercando un metodo per testare Entity Framework. Una soluzione potrebbe utilizzare un metodo di seed virtuale per generare dati sull’inizializzazione del database. Dai un’occhiata alla sezione Seed : http://blogs.msdn.com/b/adonet/archive/2010/09/02/ef-feature-ctp4-dbcontext-and-databases.aspx

    Inoltre puoi usare alcuni schemi di derisione. I più famosi che conosco sono:

    • Rhino Mock
    • Moq
    • Typemock (commerciale)

    Per visualizzare un elenco più completo di .NET mocking framework, consulta: https://stackoverflow.com/questions/37359/what-c-mocking-framework-to-use

    Un altro approccio sarebbe quello di utilizzare un provider di database in memoria come SQLite . Studiare di più C’è un provider in memoria per Entity Framework?

    Infine, ecco alcuni link utili su testing di unità Entity Framework (alcuni collegamenti fanno riferimento a Entity Framework 4.0, ma l’idea verrà raggiunta):

    http://social.msdn.microsoft.com/Forums/en/adodotnetentityframework/thread/678b5871-bec5-4640-a024-71bd4d5c77ff

    http://mosesofegypt.net/post/Introducing-Entity-Framework-Unit-Testing-with-TypeMock-Isolator.aspx

    Qual è la strada da percorrere per simulare il mio livello di database in un test unitario?

    Quello che faccio è usare un semplice object ISession ed EFSession, sono facili da prendere in giro con il mio controller, sono facili da accedere con Linq e fortemente digitato. Iniettare con DI usando Ninject.

     public interface ISession : IDisposable { void CommitChanges(); void Delete(Expression> expression) where T : class, new(); void Delete(T item) where T : class, new(); void DeleteAll() where T : class, new(); T Single(Expression> expression) where T : class, new(); IQueryable All() where T : class, new(); void Add(T item) where T : class, new(); void Add(IEnumerable items) where T : class, new(); void Update(T item) where T : class, new(); } public class EFSession : ISession { DbContext _context; public EFSession(DbContext context) { _context = context; } public void CommitChanges() { _context.SaveChanges(); } public void Delete(System.Linq.Expressions.Expression> expression) where T : class, new() { var query = All().Where(expression); foreach (var item in query) { Delete(item); } } public void Delete(T item) where T : class, new() { _context.Set().Remove(item); } public void DeleteAll() where T : class, new() { var query = All(); foreach (var item in query) { Delete(item); } } public void Dispose() { _context.Dispose(); } public T Single(System.Linq.Expressions.Expression> expression) where T : class, new() { return All().FirstOrDefault(expression); } public IQueryable All() where T : class, new() { return _context.Set().AsQueryable(); } public void Add(T item) where T : class, new() { _context.Set().Add(item); } public void Add(IEnumerable items) where T : class, new() { foreach (var item in items) { Add(item); } } ///  /// Do not use this since we use EF4, just call CommitChanges() it does not do anything ///  ///  ///  public void Update(T item) where T : class, new() { //nothing needed here } 

    Se voglio passare da EF4 a MongoDB, devo solo creare una MongoSession che implementa l’ISession …

    Stavo avendo lo stesso problema a decidere sulla progettazione generale della mia applicazione MVC. Questo progetto CodePlex di Shiju Varghese è stato di grande aiuto. È fatto in ASP.net MVC3, EF CodeFirst e utilizza anche un livello di servizio e un livello di repository. L’iniezione di dipendenza viene eseguita utilizzando Unity. È semplice e molto facile da seguire. È supportato anche da 4 post del blog molto carini. Vale la pena dare un’occhiata. E, non mollare sul repository..yet.