ASP.NET MVC3 und Entity Framework Code erste Architektur

Meine vorherige Frage brachte mich dazu, noch einmal über Schichten, Repository, Abhängigkeitsinjektionen und architektonische Sachen nachzudenken.

Meine Architektur sieht jetzt so aus:
Ich benutze zuerst den EF-Code, also habe ich nur POCO-classn und Kontext erstellt. Das schafft db und Modell.
Eine höhere Stufe sind Business-Schicht-classn (Provider). Ich benutze verschiedene Provider für jede Domain … wie MemberProvider, RoleProvider, TaskProvider etc. und ich erstelle eine neue Instanz meines DbContext in jedem dieser Provider.
Dann instanziiere ich diese Anbieter in meinen Controllern, erhalte Daten und sende sie an Views.

Meine erste Architektur beinhaltete ein Repository, das ich losgeworden bin, weil mir gesagt wurde, dass es nur noch mehr Komplexität bringt, weshalb ich nicht nur EF verwende. Ich wollte das tun .. mit EF direkt von Controllern arbeiten, aber ich muss Tests schreiben und es war ein bisschen kompliziert mit realen database. Ich musste irgendwie gefälschte Daten vortäuschen. Also habe ich für jeden Provider eine Schnittstelle erstellt und gefälschte Provider mit hartcodierten Daten in Listen erstellt. Und damit bin ich auf etwas zurückgekommen, wo ich nicht sicher bin, wie ich richtig vorgehen soll.

Diese Dinge beginnen zu schnell zu kompliziert zu werden … viele Ansätze und “Pattterns” … es erzeugt einfach zu viel Lärm und nutzlosen Code.

Gibt es eine einfache und testbare Architektur zum Erstellen und ASP.NET MVC3-Anwendung mit Entity Framework?

   

Wenn Sie TDD (oder einen anderen Testansatz mit hoher Testabdeckung) und EF zusammen verwenden möchten, müssen Sie Integrations- oder End-to-End-Tests schreiben. Das Problem hierbei ist, dass jeder Ansatz, bei dem entweder der Kontext oder das Repository verspottet wird, nur einen Test erstellt, der die Logik Ihrer oberen Schicht (die diese Mocks verwendet), aber nicht Ihre Anwendung testen kann.

Einfaches Beispiel:

Lassen Sie uns das generische Repository definieren:

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

Und lassen Sie uns eine Geschäftsmethode schreiben:

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

Jetzt, wenn Sie das Repository verspotten, werden Sie Linq-To-Objects verwenden und Sie werden einen grünen Test haben, aber wenn Sie die Anwendung mit Linq-To-Entities ausführen, erhalten Sie eine Ausnahme, weil die Auswahl von Überladung mit Indizes in L2E nicht unterstützt wird.

Dies war ein einfaches Beispiel, aber dasselbe kann passieren, wenn Methoden in Abfragen und anderen häufigen Fehlern verwendet werden. Darüber hinaus betrifft dies auch Methoden wie Hinzufügen, Aktualisieren, Löschen, die normalerweise im Repository verfügbar sind. Wenn Sie keinen Mock schreiben, der das Verhalten des EF-Kontexts und der referenziellen Integrität exakt simuliert, werden Sie Ihre Implementierung nicht testen.

Ein weiterer Teil der Geschichte sind Probleme mit Lazy Loading, die auch mit Unit Tests gegen Mocks kaum zu erkennen sind.

Aus diesem Grund sollten Sie auch Integrations- oder Ende-zu-Ende-Tests einführen, die gegen echte databaseen mit echtem EF-Kontext und L2E arbeiten. Übrigens. Für die korrekte Verwendung von TDD sind End-to-End-Tests erforderlich. Für das Schreiben von Ende-zu-Ende-Tests in ASP.NET MVC können Sie WaiN und möglicherweise auch SpecFlow für BDD verwenden, aber dies wird wirklich viel Arbeit hinzufügen, aber Sie werden Ihre Anwendung wirklich getestet haben. Wenn Sie mehr über TDD lesen möchten, empfehle ich dieses Buch (der einzige Nachteil ist, dass Beispiele in Java sind).

Integrationstests sind sinnvoll, wenn Sie kein generisches Repository verwenden und Ihre Abfragen in einer class IQueryable , die IQueryable nicht IQueryable sondern direkt Daten zurückgibt.

Beispiel:

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

Jetzt können Sie einfach einen Integrationstest schreiben, um die Implementierung dieses Repository zu testen, da Abfragen in dieser class verborgen sind und nicht den oberen Ebenen ausgesetzt sind. Aber diese Art von Repository wird irgendwie als alte Implementierung angesehen, die mit gespeicherten Prozeduren verwendet wird. Sie werden mit dieser Implementierung viele ORM-functionen verlieren, oder Sie müssen eine Menge zusätzlicher Arbeit erledigen – zum Beispiel ein Spezifikationsmuster hinzufügen, um die Abfrage in der oberen Ebene definieren zu können.

In ASP.NET MVC können Sie Ende-zu-Ende-Tests teilweise durch Integrationstests auf Controller-Ebene ersetzen.

Bearbeiten basierend auf Kommentar:

Ich sage nicht, dass Sie Komponententests, Integrationstests und End-to-End-Tests benötigen. Ich sage, dass das Erstellen von getesteten Anwendungen viel mehr Aufwand erfordert. Die Anzahl und Arten der erforderlichen Tests hängt von der Komplexität Ihrer Anwendung, der erwarteten Zukunft der Anwendung, Ihren Fähigkeiten und Fähigkeiten anderer Teammitglieder ab.

Kleine straighforward Projekte können ohne Tests erstellt werden (ok, es ist keine gute Idee, aber wir haben es gemacht und am Ende hat es geklappt) aber sobald ein Projekt einen Grenzwert überschreitet, kann man neue functionen einführen oder das Projekt warten sehr hart, weil man nie sicher ist, ob es etwas bricht, was bereits funktioniert hat – das nennt man Regression. Die beste Verteidigung gegen Regression ist ein guter Satz von automatisierten Tests.

  • Komponententests helfen Ihnen, die Methode zu testen. Solche Tests sollten idealerweise alle Ausführungspfade in der Methode abdecken. Diese Tests sollten sehr kurz und einfach zu schreiben sein – zu einem komplizierten Teil können Abhängigkeiten aufgebaut werden (Mocks, Faktes, Stubs).
  • Integrationstests helfen Ihnen, functionen über mehrere Ebenen und in der Regel über mehrere processe hinweg (Anwendung, database) zu testen. Sie müssen sie nicht für alles haben, es geht eher um Erfahrung, um auszuwählen, wo sie hilfreich sind.
  • End-to-End-Tests sind so etwas wie die validation von Anwendungsfall / User Story / Feature. Sie sollten den gesamten Ablauf der Anforderung abdecken.

Es ist nicht erforderlich, eine Feture mehrfach zu testen. Wenn Sie wissen, dass die function im End-to-End-Test getestet wird, müssen Sie keinen Integrationstest für denselben Code schreiben. Wenn Sie wissen, dass diese Methode nur einen einzigen Ausführungspfad hat, der durch den Integrationstest abgedeckt wird, müssen Sie keinen Komponententest dafür schreiben. Dies funktioniert viel besser mit dem TDD-Ansatz, bei dem Sie mit einem großen Test (End-to-End oder Integration) beginnen und tiefer in Komponententests gehen.

Abhängig von Ihrem Entwicklungsansatz müssen Sie nicht von Anfang an mit mehreren Testtypen beginnen, aber Sie können sie später einführen, wenn Ihre Anwendung komplexer wird. Die Ausnahme ist TDD / BDD, wo Sie mindestens End-to-End- und Unit-Tests verwenden sollten, bevor Sie eine einzelne Zeile anderen Codes schreiben.

Sie stellen also die falsche Frage. Die Frage ist nicht was ist einfacher? Die Frage ist, was wird Ihnen am Ende helfen und welche Komplexität passt zu Ihrer Anwendung? Wenn Sie Anwendungs- und Geschäftslogik problemlos in der Einheit testen möchten, sollten Sie den EF-Code in einige andere classn verpacken, die überspäht werden können. Aber in der gleichen Zeit müssen Sie andere Arten von Tests einführen, um sicherzustellen, dass der EF-Code funktioniert.

Ich kann Ihnen nicht sagen, welcher Ansatz zu Ihrer Umgebung / Projekt / Team / etc passt. Aber ich kann ein Beispiel aus meinem früheren Projekt erklären:

Ich habe mit zwei Kollegen ungefähr 5-6 Monate an dem Projekt gearbeitet. Das Projekt basierte auf ASP.NET MVC 2 + jQuery + EFv4 und wurde inkrementell und iterativ entwickelt. Es hatte eine Menge komplizierter Geschäftslogik und viele komplizierte databaseabfragen. Wir begannen mit generischen Repositories und einer hohen Codeabdeckung mit Komponententests und Integrationstests, um das Mapping zu validieren (einfache Tests zum Einfügen, Löschen, Aktualisieren und Auswählen von Entitäten). Nach einigen Monaten haben wir festgestellt, dass unser Ansatz nicht funktioniert. Wir hatten mehr als 1.200 Komponententests, eine Codeabdeckung von 60% (das ist nicht sehr gut) und viele Regressionsprobleme. Änderungen am EF-Modell könnten zu unerwarteten Problemen in Teilen führen, die mehrere Wochen lang nicht berührt wurden. Wir haben festgestellt, dass uns Integrationstests oder End-to-End-Tests für unsere Anwendungslogik fehlen. Die gleiche Schlussfolgerung wurde über ein paralleles Team gemacht, das an einem anderen Projekt gearbeitet hat und die Verwendung von Integrationstests wurde als Empfehlung für neue Projekte betrachtet.

Macht die Verwendung von Repository-Mustern Komplexität? In deinem Szenario glaube ich nicht. Es macht TDD einfacher und Ihr Code leichter zu verwalten. Versuchen Sie, ein generisches Repository-Muster für mehr Trennung und saubereren Code zu verwenden.

Wenn Sie mehr über TDD und Entwurfsmuster in Entity Framework erfahren möchten, casting Sie einen Blick auf: http://msdn.microsoft.com/en-us/ff714955.aspx

Es scheint jedoch so, als ob Sie nach einem Ansatz suchen, Entity Framework zu testen. Eine Lösung wäre die Verwendung einer virtuellen Startmethode zum Generieren von Daten zur databaseinitialisierung. Werfen Sie einen Blick auf Seed Abschnitt unter: http://blogs.msdn.com/b/adonet/archive/2010/09/02/ef-feature-ctp4-dbcontext-and-databases.aspx

Sie können auch einige spöttische Frameworks verwenden. Die bekanntesten sind:

  • Rhino Mocks
  • Moq
  • Typemock (kommerziell)

Eine vollständige Liste der .NET-Mocking-Frameworks finden Sie unter https://stackoverflow.com/questions/37359/what-c-mocking-framework-to-use

Ein anderer Ansatz wäre die Verwendung eines In-Memory-databaseanbieters wie SQLite . Erfahren Sie mehr unter Gibt es einen In-Memory-Anbieter für Entity Framework?

Zum Schluss noch ein paar gute Links zum Einheitentesten von Entity Framework (einige Links beziehen sich auf Entity Framework 4.0. Aber Sie werden auf die Idee kommen.):

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

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

Wie gehe ich vor, um meine databaseschicht in einem Komponententest zu fälschen?

Was ich tue, ist ein einfaches ISession- und ESSession-Objekt, das leicht in meinem Controller zu finden ist, leicht zugänglich mit Linq und stark typisiert. Inject mit DI mit 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 } 

Wenn ich von EF4 zu MongoDB wechseln möchte, muss ich nur eine MongoSession machen, die ISession implementiert …

Ich hatte das gleiche Problem bei der Entscheidung über das allgemeine Design meiner MVC-Anwendung. Dieses CodePlex-Projekt von Shiju Varghese war sehr hilfreich. Es ist in ASP.net MVC3, EF CodeFirst getan und nutzt auch eine Service-Schicht und eine Repository-Schicht. Die Abhängigkeitsinjektion erfolgt mit Unity. Es ist einfach und sehr einfach zu folgen. Es wird auch von 4 sehr schönen Blog-Posts unterstützt. Es lohnt sich, es auszuprobieren. Und geben Sie das Repository nicht auf.