Testability und Entity Framework 4,0Testability and Entity Framework 4.0

Scott allenScott Allen

Veröffentlicht: Mai 2010Published: May 2010

EinführungIntroduction

In diesem Whitepaper wird beschrieben und veranschaulicht, wie Sie testbaren Code mit den ADO.NET-Entity Framework 4,0 und Visual Studio 2010 schreiben können.This white paper describes and demonstrates how to write testable code with the ADO.NET Entity Framework 4.0 and Visual Studio 2010. In diesem Whitepaper wird nicht versucht, sich auf eine bestimmte Testmethode zu konzentrieren, wie z. b. Test gesteuerte Entwürfe (Test-gesteuerte Design) oder BDD.This paper does not try to focus on a specific testing methodology, like test-driven design (TDD) or behavior-driven design (BDD). Stattdessen konzentriert sich dieses Whitepaper darauf, wie Sie Code schreiben können, der den ADO.NET-Entity Framework verwendet, um die Isolierung und den Test auf automatisierte Weise zu testen.Instead this paper will focus on how to write code that uses the ADO.NET Entity Framework yet remains easy to isolate and test in an automated fashion. Wir betrachten allgemeine Entwurfsmuster, die das Testen in Datenzugriffs Szenarien vereinfachen und sehen, wie diese Muster bei der Verwendung des Frameworks angewendet werden.We’ll look at common design patterns that facilitate testing in data access scenarios and see how to apply those patterns when using the framework. Wir betrachten auch bestimmte Features des Frameworks, um zu sehen, wie diese Features in testbarem Code funktionieren können.We’ll also look at specific features of the framework to see how those features can work in testable code.

Was ist prüfbarer Code?What Is Testable Code?

Die Möglichkeit, eine Software Software mithilfe automatisierter Komponententests zu überprüfen, bietet zahlreiche Vorteile.The ability to verify a piece of software using automated unit tests offers many desirable benefits. Jeder weiß, dass gute Tests die Anzahl der Software Mängel in einer Anwendung reduzieren und die Qualität der Anwendung erhöhen, aber das vorhanden sein von Komponententests geht weit über das Auffinden von Fehlern hinaus.Everyone knows that good tests will reduce the number of software defects in an application and increase the application’s quality - but having unit tests in place goes far beyond just finding bugs.

Eine gute Komponenten Test Suite ermöglicht einem Entwicklungsteam, Zeit zu sparen, und die Kontrolle über die von Ihnen erstellte Software zu behalten.A good unit test suite allows a development team to save time and remain in control of the software they create. Ein Team kann Änderungen an vorhandenem Code vornehmen, die Software umgestalten, umgestalten und umstrukturieren, um neue Anforderungen zu erfüllen, und neue Komponenten zu einer Anwendung hinzufügen, während Sie wissen, dass die Test Sammlung das Verhalten der Anwendung überprüfen kann.A team can make changes to existing code, refactor, redesign, and restructure software to meet new requirements, and add new components into an application all while knowing the test suite can verify the application’s behavior. Komponententests sind Teil eines kurzen Feedbacks, um Änderungen zu vereinfachen und die Verwaltbarkeit von Software zu gewährleisten, wenn sich die Komplexität erhöht.Unit tests are part of a quick feedback cycle to facilitate change and preserve the maintainability of software as complexity increases.

Komponententests verfügen jedoch über einen Preis.Unit testing comes with a price, however. Ein Team muss die Zeit investieren, um Komponententests zu erstellen und zu verwalten.A team has to invest the time to create and maintain unit tests. Der Aufwand, der zum Erstellen dieser Tests erforderlich ist, hängt direkt mit der Prüfbarkeit der zugrunde liegenden Software zusammen.The amount of effort required to create these tests is directly related to the testability of the underlying software. Wie einfach ist die Software zu testen?How easy is the software to test? Ein Team, das Software mit Prüfbarkeit im Hinterkopf entwirft, erstellt effektive Tests schneller als das Team, das mit der nicht Test fähigen Software arbeitet.A team designing software with testability in mind will create effective tests faster than the team working with un-testable software.

Microsoft hat den ADO.NET Entity Framework 4,0 (EF4) mit Prüfbarkeit entworfen.Microsoft designed the ADO.NET Entity Framework 4.0 (EF4) with testability in mind. Dies bedeutet nicht, dass Entwickler Komponententests für den Frameworkcode selbst schreiben werden.This doesn’t mean developers will be writing unit tests against framework code itself. Stattdessen erleichtern die Testability-Ziele für EF4 das Erstellen von prüfbarem Code, der auf dem Framework aufbaut.Instead, the testability goals for EF4 make it easy to create testable code that builds on top of the framework. Bevor wir uns mit bestimmten Beispielen beschäftigen, ist es sinnvoll, die Qualität des testbaren Codes zu verstehen.Before we look at specific examples, it’s worthwhile to understand the qualities of testable code.

Die Qualitäten von testablem CodeThe Qualities of Testable Code

Code, der leicht zu testen ist, enthält immer mindestens zwei Merkmale.Code that is easy to test will always exhibit at least two traits. Zuerst ist testbarer Code leicht zu beobachten.First, testable code is easy to observe. Bei einem bestimmten Satz von Eingaben sollte die Ausgabe des Codes leicht zu beobachten sein.Given some set of inputs, it should be easy to observe the output of the code. Beispielsweise ist das Testen der folgenden Methode einfach, da die-Methode das Ergebnis einer Berechnung direkt zurückgibt.For example, testing the following method is easy because the method directly returns the result of a calculation.

    public int Add(int x, int y) {
        return x + y;
    }

Das Testen einer Methode ist schwierig, wenn die-Methode den berechneten Wert in einen Netzwerk Socket, eine Datenbanktabelle oder eine Datei wie den folgenden Code schreibt.Testing a method is difficult if the method writes the computed value into a network socket, a database table, or a file like the following code. Der Test muss zusätzliche Arbeitsschritte ausführen, um den Wert abzurufen.The test has to perform additional work to retrieve the value.

    public void AddAndSaveToFile(int x, int y) {
         var results = string.Format("The answer is {0}", x + y);
         File.WriteAllText("results.txt", results);
    }

Zweitens lässt sich der prüfbare Code leicht isolieren.Secondly, testable code is easy to isolate. Wir verwenden den folgenden Pseudo Code als ungültiges Beispiel für testbaren Code.Let’s use the following pseudo-code as a bad example of testable code.

    public int ComputePolicyValue(InsurancePolicy policy) {
        using (var connection = new SqlConnection("dbConnection"))
        using (var command = new SqlCommand(query, connection)) {

            // business calculations omitted ...               

            if (totalValue > notificationThreshold) {
                var message = new MailMessage();
                message.Subject = "Warning!";
                var client = new SmtpClient();
                client.Send(message);
            }
        }
        return totalValue;
    }

Die Methode ist leicht zu beobachten – wir können eine Versicherungsrichtlinie übergeben und überprüfen, ob der Rückgabewert mit einem erwarteten Ergebnis übereinstimmt.The method is easy to observe – we can pass in an insurance policy and verify the return value matches an expected result. Zum Testen der Methode muss jedoch eine Datenbank mit dem richtigen Schema installiert werden, und der SMTP-Server muss konfiguriert werden, falls die Methode versucht, eine e-Mail zu senden.However, to test the method we’ll need to have a database installed with the correct schema, and configure the SMTP server in case the method tries to send an email.

Der Komponenten Test möchte nur die Berechnungs Logik innerhalb der Methode überprüfen, aber der Test schlägt möglicherweise fehl, weil der e-Mail-Server offline ist oder der Datenbankserver verschoben wurde.The unit test only wants to verify the calculation logic inside the method, but the test might fail because the email server is offline, or because the database server moved. Beide Fehler stehen nicht im Zusammenhang mit dem Verhalten, das der Test überprüfen soll.Both of these failures are unrelated to the behavior the test wants to verify. Das Verhalten ist schwierig zu isolieren.The behavior is difficult to isolate.

Software Entwickler, die sich bemühen, testbaren Code zu schreiben, werden häufig bestrebt sein, Probleme im Code, den Sie schreiben, zu trennen.Software developers who strive to write testable code often strive to maintain a separation of concerns in the code they write. Die obige Methode sollte sich auf die Geschäfts Berechnungen konzentrieren und die Datenbank-und e-Mail-Implementierungsdetails an andere Komponenten delegieren.The above method should focus on the business calculations and delegate the database and email implementation details to other components. Robert C. Martin nennt dies das Prinzip der einzigen Verantwortung.Robert C. Martin calls this the Single Responsibility Principle. Ein Objekt muss eine einzelne, schmale Verantwortung Kapseln, wie z. b. die Berechnung des Werts einer Richtlinie.An object should encapsulate a single, narrow responsibility, like calculating the value of a policy. Alle anderen Datenbank-und Benachrichtigungs arbeiten sollten die Verantwortung für ein anderes Objekt haben.All other database and notification work should be the responsibility of some other object. Auf diese Weise geschriebener Code ist leichter zu isolieren, da er sich auf eine einzelne Aufgabe konzentriert.Code written in this fashion is easier to isolate because it is focused on a single task.

In .net gibt es die Abstraktionen, die wir benötigen, um das Prinzip der einzelnen Verantwortung einzuhalten und Isolation zu erzielen.In .NET we have the abstractions we need to follow the Single Responsibility Principle and achieve isolation. Wir können Schnittstellendefinitionen verwenden und erzwingen, dass der Code die Schnittstellen Abstraktion anstelle eines konkreten Typs verwendet.We can use interface definitions and force the code to use the interface abstraction instead of a concrete type. Später in diesem Whitepaper wird erläutert, wie eine Methode wie das ungültige Beispiel mit Schnittstellen funktionieren kann, die so aussehen , als Sie mit der Datenbank kommunizieren.Later in this paper we’ll see how a method like the bad example presented above can work with interfaces that look like they will talk to the database. Zum Testzeitpunkt können wir jedoch eine dummyimplementierung ersetzen, die nicht mit der Datenbank kommuniziert, sondern stattdessen Daten im Arbeitsspeicher speichert.At test time, however, we can substitute a dummy implementation that doesn’t talk to the database but instead holds data in memory. Diese Dummy-Implementierung isoliert den Code von nicht verknüpften Problemen im Datenzugriffs Code oder in der Daten Bank Konfiguration.This dummy implementation will isolate the code from unrelated problems in the data access code or database configuration.

Es gibt weitere Vorteile bei der Isolierung.There are additional benefits to isolation. Die Ausführung der Geschäfts Berechnung in der letzten Methode sollte nur wenige Millisekunden dauern, aber der Test selbst kann mehrere Sekunden dauern, da der Code das Netzwerk überspringt und mit verschiedenen Servern kommuniziert.The business calculation in the last method should only take a few milliseconds to execute, but the test itself might run for several seconds as the code hops around the network and talks to various servers. Komponententests sollten schnell ausgeführt werden, um kleine Änderungen zu vereinfachen.Unit tests should run fast to facilitate small changes. Komponententests sollten ebenfalls wiederholbar sein und nicht fehlschlagen, da eine Komponente, die nicht mit dem Test verknüpft ist, ein Problem aufweist.Unit tests should also be repeatable and not fail because a component unrelated to the test has a problem. Das Schreiben von Code, der leicht zu beobachten und zu isolieren ist, bedeutet, dass Entwickler einen einfacheren Zeitaufwand für das Schreiben von Tests für den Code haben, weniger Zeit mit dem warten auf die Ausführung von Tests verbringen und noch wichtiger ist, dass weniger Zeit für das Nachverfolgen von Fehlern aufgewendet wirdWriting code that is easy to observe and to isolate means developers will have an easier time writing tests for the code, spend less time waiting for tests to execute, and more importantly, spend less time tracking down bugs that do not exist.

Wir hoffen, dass Sie die Vorteile der Tests und der Qualität der von Test fähige Code ausgestellten Qualitäten schätzen können.Hopefully you can appreciate the benefits of testing and understand the qualities that testable code exhibits. Wir sind im Begriff, zu erfahren, wie Sie Code schreiben können, der mit EF4 verwendet wird, um Daten in einer Datenbank zu speichern, während Sie sichtbar und leicht zu isolieren ist, aber zuerst wird der Fokus auf die Erörterung testbarer Entwürfe für den Datenzugriff eingrenzen.We are about to address how to write code that works with EF4 to save data into a database while remaining observable and easy to isolate, but first we’ll narrow our focus to discuss testable designs for data access.

Entwurfsmuster für die Daten PersistenzDesign Patterns for Data Persistence

Beide ungültigen Beispiele waren zu viele Zuständigkeiten.Both of the bad examples presented earlier had too many responsibilities. Das erste ungültige Beispiel war das Ausführen einer Berechnung und das Schreiben in eine Datei.The first bad example had to perform a calculation and write to a file. Das zweite ungültige Beispiel war das Lesen von Daten aus einer Datenbank und das Ausführen einer Geschäfts Berechnung und das Senden von e-Mails.The second bad example had to read data from a database and perform a business calculation and send email. Wenn Sie kleinere Methoden entwerfen, die die Belange voneinander trennen und die Verantwortung an andere Komponenten delegieren, machen Sie große Fortschritte beim Schreiben von testbarem Code.By designing smaller methods that separate concerns and delegate responsibility to other components you’ll make great strides towards writing testable code. Das Ziel besteht darin, die Funktionalität zu erstellen, indem Aktionen aus kleinen und fokussierten Abstraktionen verfasst werden.The goal is to build functionality by composing actions from small and focused abstractions.

Wenn es um die Daten Persistenz geht, sind die kleinen und ausgerichteten Abstraktionen, nach denen wir suchen, so häufig, dass Sie als Entwurfsmuster dokumentiert wurden.When it comes to data persistence the small and focused abstractions we are looking for are so common they’ve been documented as design patterns. Martin Fowler Book Patterns of Enterprise Application Architecture war die erste Aufgabe, diese Muster im Druck zu beschreiben.Martin Fowler’s book Patterns of Enterprise Application Architecture was the first work to describe these patterns in print. In den folgenden Abschnitten wird eine kurze Beschreibung dieser Muster bereitgestellt, bevor gezeigt wird, wie diese ADO.NET Entity Framework implementiert und mit diesen Mustern arbeitet.We’ll provide a brief description of these patterns in the following sections before we show how these ADO.NET Entity Framework implements and works with these patterns.

RepositorymusterThe Repository Pattern

Fowler sagt, dass ein Repository zwischen den Domänen-und Daten Zustellungs Ebenen mithilfe einer Sammlungs ähnlichen Schnittstelle für den Zugriff auf Domänen Objekte mediiert.Fowler says a repository “mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects”. Das Ziel des Repository-Musters besteht darin, Code aus den Minutiae des Datenzugriffs zu isolieren. wie bereits erwähnt wurde, ist die Isolation ein erforderliches Merkmal für die Prüfbarkeit.The goal of the repository pattern is to isolate code from the minutiae of data access, and as we saw earlier isolation is a required trait for testability.

Der Schlüssel zur Isolation ist, wie das Repository Objekte mithilfe einer Auflistungs ähnlichen Schnittstelle verfügbar macht.The key to the isolation is how the repository exposes objects using a collection-like interface. Die Logik, die Sie schreiben, um das Repository zu verwenden, hat keine Idee, wie das Repository die von Ihnen angeforderten Objekte materialisieren soll.The logic you write to use the repository has no idea how the repository will materialize the objects you request. Das Repository kann mit einer Datenbank kommunizieren, oder es können nur Objekte aus einer Auflistung im Speicher zurückgegeben werden.The repository might talk to a database, or it might just return objects from an in-memory collection. Der gesamte Code muss wissen, dass das Repository die Auflistung beibehält, und Sie können Objekte aus der Auflistung abrufen, hinzufügen und löschen.All your code needs to know is that the repository appears to maintain the collection, and you can retrieve, add, and delete objects from the collection.

In vorhandenen .NET-Anwendungen erbt ein konkretes Repository häufig von einer generischen Schnittstelle wie der folgenden:In existing .NET applications a concrete repository often inherits from a generic interface like the following:

    public interface IRepository<T> {       
        IEnumerable<T> FindAll();
        IEnumerable<T> FindBy(Expression<Func\<T, bool>> predicate);
        T FindById(int id);
        void Add(T newEntity);
        void Remove(T entity);
    }

Wir nehmen einige Änderungen an der Schnittstellen Definition vor, wenn wir eine Implementierung für EF4 bereitstellen, das grundlegende Konzept bleibt jedoch unverändert.We’ll make a few changes to the interface definition when we provide an implementation for EF4, but the basic concept remains the same. Code kann ein konkretes Repository verwenden, das diese Schnittstelle implementiert, um eine Entität nach ihrem Primärschlüssel Wert abzurufen, eine Auflistung von Entitäten basierend auf der Auswertung eines Prädikats abzurufen oder einfach alle verfügbaren Entitäten abzurufen.Code can use a concrete repository implementing this interface to retrieve an entity by its primary key value, to retrieve a collection of entities based on the evaluation of a predicate, or simply retrieve all available entities. Der Code kann auch Entitäten über die Repository-Schnittstelle hinzufügen und entfernen.The code can also add and remove entities through the repository interface.

Bei einem IRepository von Employee-Objekten kann Code die folgenden Vorgänge ausführen.Given an IRepository of Employee objects, code can perform the following operations.

    var employeesNamedScott =
        repository
            .FindBy(e => e.Name == "Scott")
            .OrderBy(e => e.HireDate);
    var firstEmployee = repository.FindById(1);
    var newEmployee = new Employee() {/*... */};
    repository.Add(newEmployee);

Da der Code eine Schnittstelle (IRepository of Employee) verwendet, können wir den Code mit verschiedenen Implementierungen der Schnittstelle bereitstellen.Since the code is using an interface (IRepository of Employee), we can provide the code with different implementations of the interface. Eine Implementierung kann eine Implementierung sein, die von EF4 und das Beibehalten von Objekten in einer Microsoft SQL Server Datenbank unterstützt wird.One implementation might be an implementation backed by EF4 and persisting objects into a Microsoft SQL Server database. Eine andere-Implementierung (die während des Tests verwendet wird) kann durch eine in-Memory-Liste von Employee-Objekten unterstützt werden.A different implementation (one we use during testing) might be backed by an in-memory List of Employee objects. Die-Schnittstelle unterstützt Sie dabei, die Isolation im Code zu erreichen.The interface will help to achieve isolation in the code.

Beachten Sie, dass die IRepository < T- > Schnittstelle keinen Speichervorgang verfügbar macht.Notice the IRepository<T> interface does not expose a Save operation. Wie werden vorhandene Objekte aktualisiert?How do we update existing objects? Sie können über IRepository-Definitionen verfügen, die den Speichervorgang einschließen, und Implementierungen dieser Depots müssen ein Objekt direkt in der Datenbank speichern.You might come across IRepository definitions that do include the Save operation, and implementations of these repositories will need to immediately persist an object into the database. In vielen Anwendungen möchten wir Objekte jedoch nicht einzeln beibehalten.However, in many applications we don’t want to persist objects individually. Stattdessen möchten wir Objekte zum Leben bringen, vielleicht aus verschiedenen Depots, die Objekte im Rahmen einer Geschäftsaktivität ändern und dann alle Objekte im Rahmen eines einzelnen atomarischen Vorgangs beibehalten.Instead, we want to bring objects to life, perhaps from different repositories, modify those objects as part of a business activity, and then persist all the objects as part of a single, atomic operation. Glücklicherweise gibt es ein Muster, das diese Art von Verhalten zulässt.Fortunately, there is a pattern to allow this type of behavior.

Das Arbeits Einheits MusterThe Unit of Work Pattern

Fowler sagt, dass eine Arbeitseinheit eine Liste von Objekten verwaltet, die von einer Geschäftstransaktion betroffen sind, und koordiniert das Schreiben von Änderungen und die Auflösung von Parallelitäts Problemen.Fowler says a unit of work will “maintain a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems”. Es liegt in der Verantwortung der Arbeitseinheit, Änderungen an den Objekten zu verfolgen, die wir aus einem Repository in den Lebenszyklus bringen, und die Änderungen beizubehalten, die wir an den Objekten vorgenommen haben, wenn wir die Arbeitseinheit anweisen, die Änderungen zu übernehmen.It is the responsibility of the unit of work to track changes to the objects we bring to life from a repository and persist any changes we’ve made to the objects when we tell the unit of work to commit the changes. Außerdem liegt es in der Verantwortung der Arbeitseinheit, die neuen Objekte zu übernehmen, die wir allen Depots hinzugefügt haben, und die Objekte in eine Datenbank einzufügen.It’s also the responsibility of the unit of work to take the new objects we’ve added to all repositories and insert the objects into a database, and also mange deletion.

Wenn Sie schon einmal mit ADO.NET DataSets gearbeitet haben, sind Sie bereits mit dem Unit of Work-Muster vertraut.If you’ve ever done any work with ADO.NET DataSets then you’ll already be familiar with the unit of work pattern. ADO.NET Datasets konnten unsere Updates, Löschungen und Einfügungen von DataRow-Objekten nachverfolgen und konnten (mit einem TableAdapter) alle Änderungen an einer Datenbank abgleichen.ADO.NET DataSets had the ability to track our updates, deletions, and insertion of DataRow objects and could (with the help of a TableAdapter) reconcile all our changes to a database. DataSet-Objekte modellieren jedoch eine getrennte Teilmenge der zugrunde liegenden Datenbank.However, DataSet objects model a disconnected subset of the underlying database. Das Arbeitseinheits Muster weist das gleiche Verhalten auf, funktioniert jedoch mit Geschäftsobjekten und Domänen Objekten, die vom Datenzugriffs Code isoliert sind und nicht über die Datenbank verfügen.The unit of work pattern exhibits the same behavior, but works with business objects and domain objects that are isolated from data access code and unaware of the database.

Eine Abstraktion, um die Arbeitseinheit in .NET-Code zu modellieren, könnte wie folgt aussehen:An abstraction to model the unit of work in .NET code might look like the following:

    public interface IUnitOfWork {
        IRepository<Employee> Employees { get; }
        IRepository<Order> Orders { get; }
        IRepository<Customer> Customers { get; }
        void Commit();
    }

Durch das verfügbar machen von Repository-verweisen aus der Arbeitseinheit kann sichergestellt werden, dass ein einzelnes Arbeitseinheiten Objekt über die Möglichkeit verfügt, alle Entitäten zu verfolgen, die während einer Geschäftstransaktion materialisiert werden.By exposing repository references from the unit of work we can ensure a single unit of work object has the ability to track all entities materialized during a business transaction. Die Implementierung der Commit-Methode für eine echte Arbeitseinheit besteht darin, dass der gesamte Magic-Vorgang im Arbeitsspeicher mit der Datenbank abgeglichen wird.The implementation of the Commit method for a real unit of work is where all the magic happens to reconcile in-memory changes with the database. 

Bei einem iunitofwork-Verweis kann Codeänderungen an Geschäftsobjekten vornehmen, die aus einem oder mehreren Depots abgerufen wurden, und alle Änderungen mithilfe des atomarischen Commitvorgangs speichern.Given an IUnitOfWork reference, code can make changes to business objects retrieved from one or more repositories and save all the changes using the atomic Commit operation.

    var firstEmployee = unitofWork.Employees.FindById(1);
    var firstCustomer = unitofWork.Customers.FindById(1);
    firstEmployee.Name = "Alex";
    firstCustomer.Name = "Christopher";
    unitofWork.Commit();

Das Lazy Load-MusterThe Lazy Load Pattern

Fowler verwendet den Namen Lazy Load, um "ein Objekt zu beschreiben, das nicht alle benötigten Daten enthält, aber weiß, wie es zu erhalten ist".Fowler uses the name lazy load to describe “an object that doesn’t contain all of the data you need but knows how to get it”. Transparente Lazy Loading ist ein wichtiges Feature, das beim Schreiben von testbarem Geschäfts Code und beim Arbeiten mit einer relationalen Datenbank erforderlich ist.Transparent lazy loading is an important feature to have when writing testable business code and working with a relational database. Sehen Sie sich als Beispiel den folgenden Code an:As an example, consider the following code.

    var employee = repository.FindById(id);
    // ... and later ...
    foreach(var timeCard in employee.TimeCards) {
        // .. manipulate the timeCard
    }

Wie wird die Sammlung "TIMECARDS" aufgefüllt?How is the TimeCards collection populated? Es gibt zwei mögliche Antworten.There are two possible answers. Eine Antwort ist, dass das Employee-Repository bei der Aufforderung zum Abrufen eines Mitarbeiters eine Abfrage ausgibt, um den Mitarbeiter zusammen mit den zugehörigen Zeitkarten Informationen des Mitarbeiters abzurufen.One answer is that the employee repository, when asked to fetch an employee, issues a query to retrieve both the employee along with the employee’s associated time card information. In relationalen Datenbanken erfordert dies in der Regel eine Abfrage mit einer Join-Klausel und kann dazu führen, dass mehr Informationen als die Anwendungsanforderungen abgerufen werden.In relational databases this generally requires a query with a JOIN clause and may result in retrieving more information than an application needs. Was geschieht, wenn die Anwendung die TIMECARDS-Eigenschaft nie berühren muss?What if the application never needs to touch the TimeCards property?

Eine zweite Antwort besteht darin, die TIMECARDS-Eigenschaft "Bedarfs gesteuert" zu laden.A second answer is to load the TimeCards property “on demand”. Diese Lazy Loading ist implizit und transparent für die Geschäftslogik, da der Code keine speziellen APIs aufruft, um Zeitkarten Informationen abzurufen.This lazy loading is implicit and transparent to the business logic because the code does not invoke special APIs to retrieve time card information. Der Code setzt voraus, dass die Zeitkarten Informationen bei Bedarf vorhanden sind.The code assumes the time card information is present when needed. Es gibt eine Reihe von Magic-Lazy Loading, die im Allgemeinen das Lauf Zeit Abfangen von Methoden aufrufen umfasst.There is some magic involved with lazy loading that generally involves runtime interception of method invocations. Der abzurufende Code ist für die Kommunikation mit der Datenbank und das Abrufen von Zeitkarten Informationen verantwortlich, während die Geschäftslogik kostenlos als Geschäftslogik belassen wird.The intercepting code is responsible for talking to the database and retrieving time card information while leaving the business logic free to be business logic. Diese Lazy Load-Magic ermöglicht es dem Geschäfts Code, sich selbst von Datenabruf Vorgängen zu isolieren, und führt zu einem besser prüfbaren Code.This lazy load magic allows the business code to isolate itself from data retrieval operations and results in more testable code.

Der Nachteil eines verzögerten Ladens besteht darin, dass der Code eine zusätzliche Abfrage ausführt, wenn eine Anwendung die Zeitkarten Informationen benötigt.The drawback to a lazy load is that when an application does need the time card information the code will execute an additional query. Dies ist für viele Anwendungen nicht von Bedeutung, aber für Leistungs relevante Anwendungen oder Anwendungen, die eine Reihe von Mitarbeiter Objekten durchlaufen und eine Abfrage ausführen, um Zeitkarten während jeder Iterationen der Schleife abzurufen (ein Problem, das häufig als "N + 1 Query Problem" bezeichnet wird), Lazy Loading ein Drag-Vorgang ist.This isn’t a concern for many applications, but for performance sensitive applications or applications looping through a number of employee objects and executing a query to retrieve time cards during each iteration of the loop (a problem often referred to as the N+1 query problem), lazy loading is a drag. In diesen Szenarien kann es vorkommen, dass eine Anwendung Zeitkarten Informationen auf möglichst effiziente Weise lädt.In these scenarios an application might want to eagerly load time card information in the most efficient manner possible.

Glücklicherweise werden wir sehen, wie EF4 sowohl implizite Lazy Loads als auch effiziente, sorgfältige Auslastungen unterstützt, wenn wir mit dem nächsten Abschnitt fortfahren und diese Muster implementieren.Fortunately, we’ll see how EF4 supports both implicit lazy loads and efficient eager loads as we move into the next section and implement these patterns.

Implementieren von Mustern mit dem Entity FrameworkImplementing Patterns with the Entity Framework

Die gute Nachricht ist, dass alle Entwurfsmuster, die wir im letzten Abschnitt beschrieben haben, einfach mit EF4 implementiert werden können.The good news is that all of the design patterns we described in the last section are straightforward to implement with EF4. Um zu veranschaulichen, dass wir eine einfache ASP.NET MVC-Anwendung verwenden, um Mitarbeiter und die dazugehörigen Zeitkarten Informationen zu bearbeiten und anzuzeigen.To demonstrate we are going to use a simple ASP.NET MVC application to edit and display Employees and their associated time card information. Wir beginnen mit der Verwendung der folgenden "Plain Old CLR Objects" (POCOS).We’ll start by using the following “plain old CLR objects” (POCOs). 

    public class Employee {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime HireDate { get; set; }
        public ICollection<TimeCard> TimeCards { get; set; }
    }

    public class TimeCard {
        public int Id { get; set; }
        public int Hours { get; set; }
        public DateTime EffectiveDate { get; set; }
    }

Diese Klassendefinitionen ändern sich geringfügig, da wir unterschiedliche Ansätze und Features von EF4 untersuchen, aber die Absicht besteht darin, diese Klassen möglichst persistent zu halten.These class definitions will change slightly as we explore different approaches and features of EF4, but the intent is to keep these classes as persistence ignorant (PI) as possible. Ein PI-Objekt weiß nicht, wieoder auch Wennder Zustand, den er enthält, innerhalb einer Datenbank ist.A PI object doesn’t know how, or even if, the state it holds lives inside a database. Pi und POCOS sind mit Test fähiger Software Hand.PI and POCOs go hand in hand with testable software. Objekte, die einen poco-Ansatz verwenden, sind weniger eingeschränkt, flexibler und leichter zu testen, da Sie ohne eine vorhandene Datenbank betrieben werden können.Objects using a POCO approach are less constrained, more flexible, and easier to test because they can operate without a database present.

Mit den POCOS können wir eine Entity Data Model (EDM) in Visual Studio erstellen (siehe Abbildung 1).With the POCOs in place we can create an Entity Data Model (EDM) in Visual Studio (see figure 1). Der EDM wird nicht zum Generieren von Code für unsere Entitäten verwendet.We will not use the EDM to generate code for our entities. Stattdessen möchten wir die Entitäten verwenden, die wir in Hand haben.Instead, we want to use the entities we lovingly craft by hand. Wir verwenden das EDM nur zum Generieren des Datenbankschemas und zur Bereitstellung der Metadaten EF4 muss Objekte der Datenbank zuordnen.We will only use the EDM to generate our database schema and provide the metadata EF4 needs to map objects into the database.

EF-test_01

Abbildung 1Figure 1

Hinweis: Wenn Sie das EDM-Modell zuerst entwickeln möchten, ist es möglich, sauberen, Poco-Code aus dem EDM zu generieren.Note: if you want to develop the EDM model first, it is possible to generate clean, POCO code from the EDM. Dies können Sie mit einer Erweiterung von Visual Studio 2010 durchführen, die vom datenprogrammierbarkeits-Team bereitgestellt wird.You can do this with a Visual Studio 2010 extension provided by the Data Programmability team. Um die Erweiterung herunterzuladen, starten Sie den Erweiterungs-Manager über das Menü Extras in Visual Studio, und Durchsuchen Sie den Onlinekatalog der Vorlagen nach "poco" (siehe Abbildung 2).To download the extension, launch the Extension Manager from the Tools menu in Visual Studio and search the online gallery of templates for “POCO” (See figure 2). Für EF sind mehrere poco-Vorlagen verfügbar.There are several POCO templates available for EF. Weitere Informationen zur Verwendung der Vorlage finden Sie unter "Exemplarische Vorgehensweise : POCO-Vorlage für die Entity Framework".For more information on using the template, see “ Walkthrough: POCO Template for the Entity Framework”.

EF-test_02

Abbildung 2Figure 2

Von diesem poco-Ausgangspunkt aus untersuchen wir zwei unterschiedliche Ansätze für Test fähigen Code.From this POCO starting point we will explore two different approaches to testable code. Der erste Ansatz ist der EF-Ansatz, da er Abstraktionen von der Entity Framework-API nutzt, um Arbeitseinheiten und Depots zu implementieren.The first approach I call the EF approach because it leverages abstractions from the Entity Framework API to implement units of work and repositories. In der zweiten Vorgehensweise werden wir unsere eigenen benutzerdefinierten Repository-Abstraktionen erstellen und dann die vor-und Nachteile der einzelnen Ansätze sehen.In the second approach we will create our own custom repository abstractions and then see the advantages and disadvantages of each approach. Wir beginnen mit dem EF-Ansatz.We’ll start by exploring the EF approach.  

Eine EF-zentrierte ImplementierungAn EF Centric Implementation

Sehen Sie sich die folgende Controller Aktion aus einem ASP.NET-MVC-Projekt an.Consider the following controller action from an ASP.NET MVC project. Mit der Aktion wird ein Employee-Objekt abgerufen, und es wird ein Ergebnis zurückgegeben, um eine detaillierte Ansicht des Mitarbeiters anzuzeigen.The action retrieves an Employee object and returns a result to display a detailed view of the employee.

    public ViewResult Details(int id) {
        var employee = _unitOfWork.Employees
                                  .Single(e => e.Id == id);
        return View(employee);
    }

Ist der Code testable?Is the code testable? Es gibt mindestens zwei Tests, die wir benötigen, um das Verhalten der Aktion zu überprüfen.There are at least two tests we’d need to verify the action’s behavior. Zuerst möchten wir überprüfen, ob die Aktion die korrekte Ansicht zurückgibt – einen einfachen Test.First, we’d like to verify the action returns the correct view – an easy test. Wir möchten auch einen Test schreiben, um zu überprüfen, ob die Aktion den richtigen Mitarbeiter abruft, und wir möchten dies tun, ohne Code zum Abfragen der Datenbank auszuführen.We’d also want to write a test to verify the action retrieves the correct employee, and we’d like to do this without executing code to query the database. Denken Sie daran, dass der zu testende Code isoliert werden soll.Remember we want to isolate the code under test. Durch die Isolation wird sichergestellt, dass der Test aufgrund eines Fehlers im Datenzugriffs Code oder in der Daten Bank Konfiguration nicht fehlschlägt.Isolation will ensure the test doesn’t fail because of a bug in the data access code or database configuration. Wenn der Test fehlschlägt, wissen wir, dass ein Fehler in der Controller Logik und nicht in einer anderen Systemkomponente vorhanden ist.If the test fails, we will know we have a bug in the controller logic, and not in some lower level system component.

Um die Isolation zu erreichen, benötigen wir einige Abstraktionen, wie z. b. die Schnittstellen, die wir zuvor für die Depots und ArbeitseinheitenTo achieve isolation we’ll need some abstractions like the interfaces we presented earlier for repositories and units of work. Denken Sie daran, dass das Repository-Muster für die Vermittlung zwischen Domänen Objekten und der Datenzuordnung konzipiert ist.Remember the repository pattern is designed to mediate between domain objects and the data mapping layer. In diesem Szenario ist EF4 die Daten Zuordnungsschicht und stellt bereits eine Repository-ähnliche Abstraktion namens IObjectSet < T > (aus dem Namespace System. Data. Objects) bereit.In this scenario EF4 is the data mapping layer, and already provides a repository-like abstraction named IObjectSet<T> (from the System.Data.Objects namespace). Die Schnittstellen Definition sieht wie folgt aus.The interface definition looks like the following.

    public interface IObjectSet<TEntity> :
                     IQueryable<TEntity>,
                     IEnumerable<TEntity>,
                     IQueryable,
                     IEnumerable
                     where TEntity : class
    {
        void AddObject(TEntity entity);
        void Attach(TEntity entity);
        void DeleteObject(TEntity entity);
        void Detach(TEntity entity);
    }

IObjectSet < T > erfüllt die Anforderungen für ein Repository, da es einer Auflistung von Objekten (über IEnumerable < T > ) ähnelt und Methoden zum Hinzufügen und Entfernen von Objekten aus der simulierten Auflistung bereitstellt.IObjectSet<T> meets the requirements for a repository because it resembles a collection of objects (via IEnumerable<T>) and provides methods to add and remove objects from the simulated collection. Die Anfüge-und Trennmethoden machen zusätzliche Funktionen der EF4-API verfügbar.The Attach and Detach methods expose additional capabilities of the EF4 API. Um IObjectSet < T > als Schnittstelle für Repository zu verwenden, benötigen wir eine Arbeitseinheits-Abstraktion, um die Objekte gemeinsam zu binden.To use IObjectSet<T> as the interface for repositories we need a unit of work abstraction to bind repositories together.

    public interface IUnitOfWork {
        IObjectSet<Employee> Employees { get; }
        IObjectSet<TimeCard> TimeCards { get; }
        void Commit();
    }

Eine konkrete Implementierung dieser Schnittstelle wird mit SQL Server kommunizieren und ist einfach zu erstellen, indem die ObjectContext-Klasse aus EF4 verwendet wird.One concrete implementation of this interface will talk to SQL Server and is easy to create using the ObjectContext class from EF4. Die ObjectContext-Klasse ist die tatsächliche Arbeitseinheit in der EF4-API.The ObjectContext class is the real unit of work in the EF4 API.

    public class SqlUnitOfWork : IUnitOfWork {
        public SqlUnitOfWork() {
            var connectionString =
                ConfigurationManager
                    .ConnectionStrings[ConnectionStringName]
                    .ConnectionString;
            _context = new ObjectContext(connectionString);
        }

        public IObjectSet<Employee> Employees {
            get { return _context.CreateObjectSet<Employee>(); }
        }

        public IObjectSet<TimeCard> TimeCards {
            get { return _context.CreateObjectSet<TimeCard>(); }
        }

        public void Commit() {
            _context.SaveChanges();
        }

        readonly ObjectContext _context;
        const string ConnectionStringName = "EmployeeDataModelContainer";
    }

Ein IObjectSet < T > in den Lebenszyklus zu bringen, ist so einfach wie das Aufrufen der Methode "| ateobjectset" des Objekts "ObjectContext".Bringing an IObjectSet<T> to life is as easy as invoking the CreateObjectSet method of the ObjectContext object. Im Hintergrund verwendet das Framework die Metadaten, die wir im EDM bereitgestellt haben, um ein konkretes ObjectSet T zu liefern < > .Behind the scenes the framework will use the metadata we provided in the EDM to produce a concrete ObjectSet<T>. Wir behalten uns die Rückgabe der IObjectSet < T- > Schnittstelle vor, da Sie dabei helfen soll, die Prüfbarkeit im Client Code beizubehalten.We’ll stick with returning the IObjectSet<T> interface because it will help preserve testability in client code.

Diese konkrete Implementierung ist in der Produktion nützlich, aber wir müssen uns darauf konzentrieren, wie wir unsere iuniyfwork-Abstraktion verwenden, um Tests zu vereinfachen.This concrete implementation is useful in production, but we need to focus on how we’ll use our IUnitOfWork abstraction to facilitate testing.

Die Test DoublesThe Test Doubles

Um die Controller Aktion zu isolieren, benötigen wir die Möglichkeit, zwischen der realen Arbeitseinheit (gestützt durch einen ObjectContext) und einer Test Double-oder "Fake"-Arbeitseinheit zu wechseln (Durchführung von in-Memory-Vorgängen).To isolate the controller action we’ll need the ability to switch between the real unit of work (backed by an ObjectContext) and a test double or “fake” unit of work (performing in-memory operations). Der gängige Ansatz zum Durchführen dieser Art von Umschaltung besteht darin, dass der MVC-Controller nicht eine Arbeitseinheit instanziiert, sondern stattdessen die Arbeitseinheit als Konstruktorparameter an den Controller übergibt.The common approach to perform this type of switching is to not let the MVC controller instantiate a unit of work, but instead pass the unit of work into the controller as a constructor parameter.

    class EmployeeController : Controller {
      publicEmployeeController(IUnitOfWork unitOfWork)  {
          _unitOfWork = unitOfWork;
      }
      ...
    }

Der obige Code ist ein Beispiel für eine Abhängigkeitsinjektion.The above code is an example of dependency injection. Wir gestatten dem Controller nicht, seine Abhängigkeit (die Arbeitseinheit) zu erstellen, sondern die Abhängigkeit in den Controller einzufügen.We don’t allow the controller to create it’s dependency (the unit of work) but inject the dependency into the controller. In einem MVC-Projekt ist es üblich, eine benutzerdefinierte Controller-Factory in Kombination mit einem IOC-Container (Inversion of Control) zu verwenden, um die Abhängigkeitsinjektion zu automatisieren.In an MVC project it is common to use a custom controller factory in combination with an inversion of control (IoC) container to automate dependency injection. Diese Themen gehen über den Rahmen dieses Artikels hinaus, aber Sie können weitere Informationen lesen, indem Sie die Verweise am Ende dieses Artikels befolgen.These topics are beyond the scope of this article, but you can read more by following the references at the end of this article.

Eine gefälschte Arbeitseinheit, die wir für Tests verwenden können, könnte wie folgt aussehen.A fake unit of work implementation that we can use for testing might look like the following.

    public class InMemoryUnitOfWork : IUnitOfWork {
        public InMemoryUnitOfWork() {
            Committed = false;
        }
        public IObjectSet<Employee> Employees {
            get;
            set;
        }

        public IObjectSet<TimeCard> TimeCards {
            get;
            set;
        }

        public bool Committed { get; set; }
        public void Commit() {
            Committed = true;
        }
    }

Beachten Sie, dass die gefälschte Arbeitseinheit eine durch ein Commit getrennte Eigenschaft verfügbar macht.Notice the fake unit of work exposes a Commited property. Es ist manchmal hilfreich, einer gefälschten Klasse Features hinzuzufügen, die das Testen vereinfachen.It’s sometimes useful to add features to a fake class that facilitate testing. In diesem Fall ist es leicht zu beobachten, ob der Code einen Commit für eine Arbeitseinheit durchführt, indem er die Eigenschaft "komprimiert" überprüft.In this case it is easy to observe if code commits a unit of work by checking the Commited property.

Wir benötigen auch ein gefälschtes IObjectSet < T > , das Mitarbeiter-und Timecard-Objekte im Arbeitsspeicher hält.We’ll also need a fake IObjectSet<T> to hold Employee and TimeCard objects in memory. Wir können eine einzelne Implementierung mit Generika bereitstellen.We can provide a single implementation using generics.

    public class InMemoryObjectSet<T> : IObjectSet<T> where T : class
        public InMemoryObjectSet()
            : this(Enumerable.Empty<T>()) {
        }
        public InMemoryObjectSet(IEnumerable<T> entities) {
            _set = new HashSet<T>();
            foreach (var entity in entities) {
                _set.Add(entity);
            }
            _queryableSet = _set.AsQueryable();
        }
        public void AddObject(T entity) {
            _set.Add(entity);
        }
        public void Attach(T entity) {
            _set.Add(entity);
        }
        public void DeleteObject(T entity) {
            _set.Remove(entity);
        }
        public void Detach(T entity) {
            _set.Remove(entity);
        }
        public Type ElementType {
            get { return _queryableSet.ElementType; }
        }
        public Expression Expression {
            get { return _queryableSet.Expression; }
        }
        public IQueryProvider Provider {
            get { return _queryableSet.Provider; }
        }
        public IEnumerator<T> GetEnumerator() {
            return _set.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        readonly HashSet<T> _set;
        readonly IQueryable<T> _queryableSet;
    }

Bei diesem Test wird der größte Teil seiner Arbeit an ein zugrunde liegendes HashSet < T- > Objekt delegiert.This test double delegates most of its work to an underlying HashSet<T> object. Beachten Sie, dass IObjectSet < t > eine generische Einschränkung erfordert, die T als Klasse (einen Verweistyp) erzwingt, und erzwingt, dass "iquerable t" implementiert wird < > .Note that IObjectSet<T> requires a generic constraint enforcing T as a class (a reference type), and also forces us to implement IQueryable<T>. Es ist einfach, eine Auflistung im Arbeitsspeicher als "iquerable T" < zu > verwenden, indem der standardmäßige LINQ-Operator "asquerable" verwendet wird.It is easy to make an in-memory collection appear as an IQueryable<T> using the standard LINQ operator AsQueryable.

Die TestsThe Tests

Bei herkömmlichen Unittests wird eine einzelne Testklasse verwendet, um alle Tests für alle Aktionen in einem einzelnen MVC-Controller aufzunehmen.Traditional unit tests will use a single test class to hold all of the tests for all of the actions in a single MVC controller. Wir können diese Tests oder eine beliebige Art von Komponenten Test mithilfe der in Arbeitsspeicher-Fakes, die wir erstellt haben, schreiben.We can write these tests, or any type of unit test, using the in memory fakes we’ve built. In diesem Artikel vermeiden wir jedoch die monolithische Test Klassen Herangehensweise und Gruppieren unsere Tests, um sich auf eine bestimmte Funktionalität zu konzentrieren.However, for this article we will avoid the monolithic test class approach and instead group our tests to focus on a specific piece of functionality.Beispielsweise könnte "Create New Employee" die Funktionalität sein, die wir testen möchten. Daher verwenden wir eine einzelne Testklasse, um die einzige Controller Aktion zu überprüfen, die für die Erstellung eines neuen Mitarbeiters zuständig ist.  For example, “create new employee” might be the functionality we want to test, so we will use a single test class to verify the single controller action responsible for creating a new employee.

Es gibt einige gängige Setup Codes, die wir für alle diese differenzierten Test Klassen benötigen.There is some common setup code we need for all these fine grained test classes. Beispielsweise müssen wir immer unsere in-Memory-Repository und gefälschte Arbeitseinheit erstellen.For example, we always need to create our in-memory repositories and fake unit of work. Außerdem benötigen wir eine Instanz des Employee-Controllers, bei der die gefälschte Arbeitseinheit eingefügt wurde.We also need an instance of the employee controller with the fake unit of work injected. Wir geben diesen allgemeinen Setup Code über Test Klassen hinweg mithilfe einer Basisklasse frei.We’ll share this common setup code across test classes by using a base class.

    public class EmployeeControllerTestBase {
        public EmployeeControllerTestBase() {
            _employeeData = EmployeeObjectMother.CreateEmployees()
                                                .ToList();
            _repository = new InMemoryObjectSet<Employee>(_employeeData);
            _unitOfWork = new InMemoryUnitOfWork();
            _unitOfWork.Employees = _repository;
            _controller = new EmployeeController(_unitOfWork);
        }

        protected IList<Employee> _employeeData;
        protected EmployeeController _controller;
        protected InMemoryObjectSet<Employee> _repository;
        protected InMemoryUnitOfWork _unitOfWork;
    }

Die "Objekt-Mutter", die in der Basisklasse verwendet wird, ist ein gängiges Muster zum Erstellen von Testdaten.The “object mother” we use in the base class is one common pattern for creating test data. Eine Objekt-Mutter enthält Factorymethoden zum Instanziieren von Test Entitäten für die Verwendung in mehreren Test Vorrichtungen.An object mother contains factory methods to instantiate test entities for use across multiple test fixtures.

    public static class EmployeeObjectMother {
        public static IEnumerable<Employee> CreateEmployees() {
            yield return new Employee() {
                Id = 1, Name = "Scott", HireDate=new DateTime(2002, 1, 1)
            };
            yield return new Employee() {
                Id = 2, Name = "Poonam", HireDate=new DateTime(2001, 1, 1)
            };
            yield return new Employee() {
                Id = 3, Name = "Simon", HireDate=new DateTime(2008, 1, 1)
            };
        }
        // ... more fake data for different scenarios
    }

Wir können "Mitarbeiter Controller TestBase" als Basisklasse für eine Reihe von Test Vorrichtungen verwenden (siehe Abbildung 3).We can use the EmployeeControllerTestBase as the base class for a number of test fixtures (see figure 3). Jede Test Fixierung testet eine bestimmte Controller Aktion.Each test fixture will test a specific controller action. Beispielsweise konzentriert sich eine Test Fixierung auf das Testen der Erstellungs Aktion, die während einer HTTP GET-Anforderung verwendet wird (um die Ansicht zum Erstellen eines Mitarbeiters anzuzeigen), und eine andere Fixierung konzentriert sich auf die CREATE-Aktion, die in einer HTTP POST-Anforderung verwendet wird (um die vom Benutzer gesendeten Informationen zu erstellen, um einen Mitarbeiter zu erstellen).For example, one test fixture will focus on testing the Create action used during an HTTP GET request (to display the view for creating an employee), and a different fixture will focus on the Create action used in an HTTP POST request (to take information submitted by the user to create an employee). Jede abgeleitete Klasse ist nur für das Setup verantwortlich, das in Ihrem speziellen Kontext benötigt wird, und um die Assertionen bereitzustellen, die zum Überprüfen der Ergebnisse für den jeweiligen Test Kontext erforderlich sind.Each derived class is only responsible for the setup needed in its specific context, and to provide the assertions needed to verify the outcomes for its specific test context.

EF-test_03

Abbildung 3Figure 3

Die hier dargestellten Benennungs Konventionen und teststile sind für testbaren Code nicht erforderlich – es handelt sich nur um einen Ansatz.The naming convention and test style presented here isn’t required for testable code – it’s just one approach. Abbildung 4 zeigt die Tests, die im Jet Brains reschärfere Test Runner-Plug-in für Visual Studio 2010 ausgeführt werden.Figure 4 shows the tests running in the Jet Brains Resharper test runner plugin for Visual Studio 2010.

EF-test_04

Abbildung 4Figure 4

Mit einer Basisklasse zur Handhabung des freigegebenen Setup Codes sind die Komponententests für die einzelnen Controller Aktionen klein und leicht zu schreiben.With a base class to handle the shared setup code, the unit tests for each controller action are small and easy to write. Die Tests werden schnell ausgeführt (da wir in-Memory-Vorgänge durchführen) und sollten aufgrund von nicht verknüpften Infrastrukturen oder umweltbezogenen Belangen fehlschlagen (da wir die zu testende Einheit isoliert haben).The tests will execute quickly (since we are performing in-memory operations), and shouldn’t fail because of unrelated infrastructure or environmental concerns (because we’ve isolated the unit under test).

    [TestClass]
    public class EmployeeControllerCreateActionPostTests
               : EmployeeControllerTestBase {
        [TestMethod]
        public void ShouldAddNewEmployeeToRepository() {
            _controller.Create(_newEmployee);
            Assert.IsTrue(_repository.Contains(_newEmployee));
        }
        [TestMethod]
        public void ShouldCommitUnitOfWork() {
            _controller.Create(_newEmployee);
            Assert.IsTrue(_unitOfWork.Committed);
        }
        // ... more tests

        Employee _newEmployee = new Employee() {
            Name = "NEW EMPLOYEE",
            HireDate = new System.DateTime(2010, 1, 1)
        };
    }

In diesen Tests führt die Basisklasse den größten Teil der Einrichtung aus.In these tests, the base class does most of the setup work. Beachten Sie, dass der Basisklassenkonstruktor das in-Memory-Repository, eine gefälschte Arbeitseinheit und eine Instanz der Klasse Mitarbeiter Controller erstellt.Remember the base class constructor creates the in-memory repository, a fake unit of work, and an instance of the EmployeeController class. Die Testklasse wird von dieser Basisklasse abgeleitet und konzentriert sich auf die Besonderheiten beim Testen der Create-Methode.The test class derives from this base class and focuses on the specifics of testing the Create method. In diesem Fall werden die Besonderheiten in den Schritten "anordnen, Act und Assert" angezeigt, die Sie bei jedem Komponenten Testverfahren sehen:In this case the specifics boil down to the “arrange, act, and assert” steps you’ll see in any unit testing procedure:

  • Erstellen Sie ein netwemployee-Objekt, um eingehende Daten zu simulieren.Create a newEmployee object to simulate incoming data.
  • Rufen Sie die CREATE-Aktion von Mitarbeiter Controller auf, und übergeben Sie die Datei "netwemployee".Invoke the Create action of the EmployeeController and pass in the newEmployee.
  • Überprüfen Sie, ob die CREATE-Aktion die erwarteten Ergebnisse erzeugt (der Mitarbeiter wird im Repository angezeigt).Verify the Create action produces the expected results (the employee appears in the repository).

Wir haben die Möglichkeit, alle Mitarbeiter zu testen, die wir erstellt haben.What we’ve built allows us to test any of the EmployeeController actions. Wenn wir z. b. Tests für die Index Aktion des Employee-Controllers schreiben, können wir von der Test Basisklasse erben, um das gleiche Basis Setup für unsere Tests einzurichten.For example, when we write tests for the Index action of the Employee controller we can inherit from the test base class to establish the same base setup for our tests. Erneut erstellt die Basisklasse das in-Memory-Repository, die gefälschte Arbeitseinheit und eine Instanz von Mitarbeiter Controller.Again the base class will create the in-memory repository, the fake unit of work, and an instance of the EmployeeController. Die Tests für die Index Aktion müssen sich nur auf das Aufrufen der Index Aktion und das Testen der Qualitäten des Modells konzentrieren, das von der Aktion zurückgegeben wird.The tests for the Index action only need to focus on invoking the Index action and testing the qualities of the model the action returns.

    [TestClass]
    public class EmployeeControllerIndexActionTests
               : EmployeeControllerTestBase {
        [TestMethod]
        public void ShouldBuildModelWithAllEmployees() {
            var result = _controller.Index();
            var model = result.ViewData.Model
                          as IEnumerable<Employee>;
            Assert.IsTrue(model.Count() == _employeeData.Count);
        }
        [TestMethod]
        public void ShouldOrderModelByHiredateAscending() {
            var result = _controller.Index();
            var model = result.ViewData.Model
                         as IEnumerable<Employee>;
            Assert.IsTrue(model.SequenceEqual(
                           _employeeData.OrderBy(e => e.HireDate)));
        }
        // ...
    }

Die Tests, die wir mit in-Memory-Fakes erstellen, sind darauf ausgerichtet, den Zustand der Software zu testen.The tests we are creating with in-memory fakes are oriented towards testing the state of the software. Wenn Sie z. b. die Erstellungs Aktion testen möchten, soll der Status des Repository nach der Ausführung der CREATE-Aktion überprüft werden – enthält das Repository den neuen Mitarbeiter?For example, when testing the Create action we want to inspect the state of the repository after the create action executes – does the repository hold the new employee?

    [TestMethod]
    public void ShouldAddNewEmployeeToRepository() {
        _controller.Create(_newEmployee);
        Assert.IsTrue(_repository.Contains(_newEmployee));
    }

Später befassen wir uns mit Interaktions basierten Tests.Later we’ll look at interaction based testing. Durch Interaktions basierte Tests wird gefragt, ob der zu testende Code die richtigen Methoden für unsere Objekte aufgerufen hat und die richtigen Parameter übergebenen.Interaction based testing will ask if the code under test invoked the proper methods on our objects and passed the correct parameters. Im folgenden wird ein weiteres Entwurfsmuster behandelt – das verzögerte Laden.For now we’ll move on the cover another design pattern – the lazy load.

Unverzügliches Laden und Lazy LoadEager Loading and Lazy Loading

An einem bestimmten Punkt in der ASP.NET MVC-Webanwendung möchten wir möglicherweise die Informationen eines Mitarbeiters anzeigen und die zugeordneten Zeitkarten des Mitarbeiters einschließen.At some point in the ASP.NET  MVC web application we might wish to display an employee’s information and include the employee’s associated time cards. Beispielsweise kann eine Zeitkarten-Übersichts Anzeige angezeigt werden, in der der Name des Mitarbeiters und die Gesamtzahl der Zeitkarten im System angezeigt werden.For example, we might have a time card summary display that shows the employee’s name and the total number of time cards in the system. Es gibt mehrere Ansätze, die wir zur Implementierung dieser Funktion verwenden können.There are several approaches we can take to implement this feature.

ProjektionProjection

Ein einfacher Ansatz zum Erstellen der Zusammenfassung besteht darin, ein Modell zu erstellen, das für die Informationen vorgesehen ist, die in der Ansicht angezeigt werden sollen.One easy approach to create the summary is to construct a model dedicated to the information we want to display in the view. In diesem Szenario könnte das Modell wie folgt aussehen.In this scenario the model might look like the following.

    public class EmployeeSummaryViewModel {
        public string Name { get; set; }
        public int TotalTimeCards { get; set; }
    }

Beachten Sie, dass es sich bei "Mitarbeiter Name" nicht um eine Entität handelt – das heißt, dass es sich nicht um etwas handelt, das in der Datenbank persistent gespeichert werden soll.Note that the EmployeeSummaryViewModel is not an entity – in other words it is not something we want to persist in the database. Diese Klasse wird nur verwendet, um Daten in einer stark typisierten Weise in die Ansicht zu mischen.We are only going to use this class to shuffle data into the view in a strongly typed manner. Das Ansichts Modell ist wie ein Datenübertragungs Objekt (Data Transfer Object, dto), da es kein Verhalten (keine Methoden) – nur Eigenschaften enthält.The view model is like a data transfer object (DTO) because it contains no behavior (no methods) – only properties. Die Eigenschaften enthalten die Daten, die wir verschieben müssen.The properties will hold the data we need to move. Es ist einfach, dieses Ansichts Modell mit dem Standard Projektions Operator von LINQ – dem Select-Operator zu instanziieren.It is easy to instantiate this view model using LINQ’s standard projection operator – the Select operator.

    public ViewResult Summary(int id) {
        var model = _unitOfWork.Employees
                               .Where(e => e.Id == id)
                               .Select(e => new EmployeeSummaryViewModel
                                  {
                                    Name = e.Name,
                                    TotalTimeCards = e.TimeCards.Count()
                                  })
                               .Single();
        return View(model);
    }

Der obige Code enthält zwei wichtige Features.There are two notable features to the above code. Zuerst – der Code ist einfach zu testen, da er immer noch leicht zu beobachten und zu isolieren ist.First – the code is easy to test because it is still easy to observe and isolate. Der Select-Operator funktioniert genauso gut wie bei den in-Memory-Fakes, wie es bei der realen Arbeitseinheit der Fall ist.The Select operator works just as well against our in-memory fakes as it does against the real unit of work.

    [TestClass]
    public class EmployeeControllerSummaryActionTests
               : EmployeeControllerTestBase {
        [TestMethod]
        public void ShouldBuildModelWithCorrectEmployeeSummary() {
            var id = 1;
            var result = _controller.Summary(id);
            var model = result.ViewData.Model as EmployeeSummaryViewModel;
            Assert.IsTrue(model.TotalTimeCards == 3);
        }
        // ...
    }

Das zweite wichtige Feature ist, wie der Code es EF4 ermöglicht, eine einzelne, effiziente Abfrage zu generieren, um Mitarbeiter-und Zeitkarten Informationen zusammenzustellen.The second notable feature is how the code allows EF4 to generate a single, efficient query to assemble employee and time card information together. Wir haben Mitarbeiter Informationen und Zeitkarten Informationen in dasselbe Objekt geladen, ohne dass spezielle APIs verwendet werden.We’ve loaded employee information and time card information into the same object without using any special APIs. Der Code hat lediglich die erforderlichen Informationen zum Verwenden von standardmäßigen LINQ-Operatoren ausgedrückt, die für in-Memory-Datenquellen und Remote Datenquellen geeignet sind.The code merely expressed the information it requires using standard LINQ operators that work against in-memory data sources as well as remote data sources. EF4 konnte die von der LINQ-Abfrage und dem C-Compiler generierten Ausdrucks Baumstrukturen # in eine einzelne und effiziente T-SQL-Abfrage übersetzen.EF4 was able to translate the expression trees generated by the LINQ query and C# compiler into a single and efficient T-SQL query.

    SELECT
    [Limit1].[Id] AS [Id],
    [Limit1].[Name] AS [Name],
    [Limit1].[C1] AS [C1]
    FROM (SELECT TOP (2)
      [Project1].[Id] AS [Id],
      [Project1].[Name] AS [Name],
      [Project1].[C1] AS [C1]
      FROM (SELECT
        [Extent1].[Id] AS [Id],
        [Extent1].[Name] AS [Name],
        (SELECT COUNT(1) AS [A1]
         FROM [dbo].[TimeCards] AS [Extent2]
         WHERE [Extent1].[Id] =
               [Extent2].[EmployeeTimeCard_TimeCard_Id]) AS [C1]
              FROM [dbo].[Employees] AS [Extent1]
               WHERE [Extent1].[Id] = @p__linq__0
         )  AS [Project1]
    )  AS [Limit1]

In anderen Zeiten möchten wir nicht mit einem Ansichts Modell oder DTO-Objekt arbeiten, sondern mit echten Entitäten.There are other times when we don’t want to work with a view model or DTO object, but with real entities. Wenn wir wissen, dass wir einen Mitarbeiter und die Zeitkarten des Mitarbeiters benötigen, können wir die verknüpften Daten auf unaufdringliche und effiziente Weise laden.When we know we need an employee and the employee’s time cards, we can eagerly load the related data in an unobtrusive and efficient manner.

Explizites LadenExplicit Eager Loading

Wenn Sie zusammen gehörige Entitäts Informationen laden möchten, benötigen wir einige Mechanismen für die Geschäftslogik (oder in diesem Szenario, Controller Aktions Logik), um den Wunsch zum Repository auszudrücken.When we want to eagerly load related entity information we need some mechanism for business logic (or in this scenario, controller action logic) to express its desire to the repository. Die Klasse EF4 ObjectQuery < T > definiert eine include-Methode, um die verknüpften Objekte anzugeben, die während einer Abfrage abgerufen werden sollen.The EF4 ObjectQuery<T> class defines an Include method to specify the related objects to retrieve during a query. Beachten Sie, dass EF4 ObjectContext Entitäten über die konkrete ObjectSet t-Klasse verfügbar macht < > , die von ObjectQuery t erbt < > .Remember the EF4 ObjectContext exposes entities via the concrete ObjectSet<T> class which inherits from ObjectQuery<T>.Wenn wir ObjectSet T- < > Verweise in unserer Controller Aktion verwenden, könnten wir den folgenden Code schreiben, um die Zeitkarten Informationen für die einzelnen Mitarbeiter zu verwenden.  If we were using ObjectSet<T> references in our controller action we could write the following code to specify an eager load of time card information for each employee.

    _employees.Include("TimeCards")
              .Where(e => e.HireDate.Year > 2009);

Da wir jedoch versuchen, den Code testfähig zu halten, machen wir ObjectSet T nicht < > von außerhalb der realen Arbeitseinheits Klasse verfügbar.However, since we are trying to keep our code testable we are not exposing ObjectSet<T> from outside the real unit of work class. Stattdessen wird die IObjectSet < t- > Schnittstelle verwendet, die einfacher zu fälschen ist, aber IObjectSet < t > definiert keine Include-Methode.Instead, we rely on the IObjectSet<T> interface which is easier to fake, but IObjectSet<T> does not define an Include method. Die Schönheit von LINQ besteht darin, dass wir unseren eigenen Include-Operator erstellen können.The beauty of LINQ is that we can create our own Include operator.

    public static class QueryableExtensions {
        public static IQueryable<T> Include<T>
                (this IQueryable<T> sequence, string path) {
            var objectQuery = sequence as ObjectQuery<T>;
            if(objectQuery != null)
            {
                return objectQuery.Include(path);
            }
            return sequence;
        }
    }

Beachten Sie, dass dieser Include-Operator als Erweiterungsmethode für iquerable < t > anstelle von IObjectSet t definiert ist < > .Notice this Include operator is defined as an extension method for IQueryable<T> instead of IObjectSet<T>. Dies ermöglicht uns die Verwendung der-Methode mit einer breiteren Palette möglicher Typen, einschließlich iquervable < t > , IObjectSet < t > , ObjectQuery < t > und ObjectSet < t > .This gives us the ability to use the method with a wider range of possible types, including IQueryable<T>, IObjectSet<T>, ObjectQuery<T>, and ObjectSet<T>. Wenn die zugrunde liegende Sequenz keine echte EF4 ObjectQuery < T ist > , wird keine Beschädigung durchgeführt, und der Include-Operator ist ein No-op.In the event the underlying sequence is not a genuine EF4 ObjectQuery<T>, then there is no harm done and the Include operator is a no-op. Wenn die zugrunde liegende is Sequenz ein ObjectQuery < t > (oder von ObjectQuery < t abgeleitet > ) ist, werden die Anforderungen für zusätzliche Daten von EF4 angezeigt, und die richtige SQL-Abfrage wird formuliert.If the underlying sequence is an ObjectQuery<T> (or derived from ObjectQuery<T>), then EF4 will see our requirement for additional data and formulate the proper SQL query.

Mit diesem neuen Operator können wir explizit eine Zeitkarten Informationen aus dem Repository anfordern.With this new operator in place we can explicitly request an eager load of time card information from the repository.

    public ViewResult Index() {
        var model = _unitOfWork.Employees
                               .Include("TimeCards")
                               .OrderBy(e => e.HireDate);
        return View(model);
    }

Beim Ausführen für einen echten ObjectContext erzeugt der Code die folgende einzelne Abfrage.When run against a real ObjectContext, the code produces the following single query. Die Abfrage sammelt in einer einzigen Fahrt genügend Informationen aus der Datenbank, um die Mitarbeiter Objekte zu materialisieren und ihre TIMECARDS-Eigenschaft vollständig aufzufüllen.The query gathers enough information from the database in one trip to materialize the employee objects and fully populate their TimeCards property.

    SELECT
    [Project1].[Id] AS [Id],
    [Project1].[Name] AS [Name],
    [Project1].[HireDate] AS [HireDate],
    [Project1].[C1] AS [C1],
    [Project1].[Id1] AS [Id1],
    [Project1].[Hours] AS [Hours],
    [Project1].[EffectiveDate] AS [EffectiveDate],
    [Project1].[EmployeeTimeCard_TimeCard_Id] AS [EmployeeTimeCard_TimeCard_Id]
    FROM ( SELECT
         [Extent1].[Id] AS [Id],
         [Extent1].[Name] AS [Name],
         [Extent1].[HireDate] AS [HireDate],
         [Extent2].[Id] AS [Id1],
         [Extent2].[Hours] AS [Hours],
         [Extent2].[EffectiveDate] AS [EffectiveDate],
         [Extent2].[EmployeeTimeCard_TimeCard_Id] AS
                    [EmployeeTimeCard_TimeCard_Id],
         CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int)
         ELSE 1 END AS [C1]
         FROM  [dbo].[Employees] AS [Extent1]
         LEFT OUTER JOIN [dbo].[TimeCards] AS [Extent2] ON [Extent1].[Id] = [Extent2].[EmployeeTimeCard_TimeCard_Id]
    )  AS [Project1]
    ORDER BY [Project1].[HireDate] ASC,
             [Project1].[Id] ASC, [Project1].[C1] ASC

Die großartige Nachricht ist, dass der Code innerhalb der Aktionsmethode vollständig getestet werden kann.The great news is the code inside the action method remains fully testable. Wir müssen keine zusätzlichen Features für unsere Fakes bereitstellen, um den Include-Operator zu unterstützen.We don’t need to provide any additional features for our fakes to support the Include operator. Die schlechte Nachricht ist, dass wir den Include-Operator in dem Code verwenden mussten, der persistent bleiben sollte.The bad news is we had to use the Include operator inside of the code we wanted to keep persistence ignorant. Dies ist ein gutes Beispiel für die Art von vor-und Nachteile, die Sie bei der Erstellung von Test barem Code auswerten müssen.This is a prime example of the type of tradeoffs you’ll need to evaluate when building testable code. In manchen Zeiten ist es erforderlich, dass Persistenzprobleme außerhalb der Repository-Abstraktion verbleibt, um Leistungsziele zu erreichen.There are times when you need to let persistence concerns leak outside the repository abstraction to meet performance goals.

Die Alternative zum Eager Loading ist Lazy Loading.The alternative to eager loading is lazy loading. Lazy Load bedeutet, dass der Geschäfts Code nicht erforderlich ist, um die Anforderung für die zugehörigen Daten explizit anzukündigen.Lazy loading means we do not need our business code to explicitly announce the requirement for associated data. Stattdessen werden unsere Entitäten in der Anwendung verwendet, und wenn zusätzliche Daten benötigt werden Entity Framework werden die Daten bei Bedarf geladen.Instead, we use our entities in the application and if additional data is needed Entity Framework will load the data on demand.

Lazy LoadingLazy Loading

Es ist leicht vorstellbar, ein Szenario zu finden, in dem wir nicht wissen, welche Daten eine Geschäftslogik benötigt.It’s easy to imagine a scenario where we don’t know what data a piece of business logic will need. Möglicherweise wissen Sie, dass die Logik ein Employee-Objekt benötigt, aber wir können in verschiedene Ausführungs Pfade verzweigen, in denen einige dieser Pfade Zeitkarten Informationen vom Mitarbeiter erfordern, und andere nicht.We might know the logic needs an employee object, but we may branch into different execution paths where some of those paths require time card information from the employee, and some do not. Szenarios wie diese eignen sich hervorragend für implizite Lazy Loading, da Daten nach Bedarf nach Bedarf angezeigt werden.Scenarios like this are perfect for implicit lazy loading because data magically appears on an as-needed basis.

Lazy Load, auch als verzögertes Laden bezeichnet, erfüllt einige Anforderungen an unsere Entitäts Objekte.Lazy loading, also known as deferred loading, does place some requirements on our entity objects. POCOS mit der wahren Persistenz von Persistenz würde keinerlei Anforderungen von der Persistenzebene genügen, aber es ist praktisch unmöglich, eine echte Persistenz zu gewährleisten.POCOs with true persistence ignorance would not face any requirements from the persistence layer, but true persistence ignorance is practically impossible to achieve.Stattdessen messen wir die Persistenz der Persistenz in relativen Grad.  Instead we measure persistence ignorance in relative degrees. Es wäre bedauerlich, wenn wir von einer persistenzorientierten Basisklasse erben oder eine spezialisierte Sammlung verwenden müssen, um Lazy Loading in POCOS zu erzielen.It would be unfortunate if we needed to inherit from a persistence oriented base class or use a specialized collection to achieve lazy loading in POCOs. Glücklicherweise hat EF4 eine weniger eindringliche Lösung.Fortunately, EF4 has a less intrusive solution.

Nahezu nicht erkennbarVirtually Undetectable

Wenn poco-Objekte verwendet werden, kann EF4 dynamisch Lauf Zeit Proxys für Entitäten generieren.When using POCO objects, EF4 can dynamically generate runtime proxies for entities. Diese Proxys wrappen die materialisierten POCOS unsichtbar und bieten zusätzliche Dienste, indem jeder Get-und Set-Vorgang der einzelnen Eigenschaften abgefangen wird, um zusätzliche Aufgaben auszuführen.These proxies invisibly wrap the materialized POCOs and provide additional services by intercepting each property get and set operation to perform additional work. Ein solcher Dienst ist das Lazy Loading Feature, nach dem wir suchen.One such service is the lazy loading feature we are looking for. Ein anderer Dienst ist ein effizienter Mechanismus zur Änderungs Nachverfolgung, der aufzeichnen kann, wann das Programm die Eigenschaftswerte einer Entität ändert.Another service is an efficient change tracking mechanism which can record when the program changes the property values of an entity. Die Liste der Änderungen wird von ObjectContext während der SaveChanges-Methode verwendet, um alle geänderten Entitäten mithilfe von Update-Befehlen beizubehalten.The list of changes is used by the ObjectContext during the SaveChanges method to persist any modified entities using UPDATE commands.

Damit diese Proxys funktionieren, benötigen Sie jedoch eine Möglichkeit, mit den Get-und Set-Vorgängen für eine Entität zu verbinden, und die Proxys erreichen dieses Ziel durch Überschreiben von virtuellen Membern.For these proxies to work, however, they need a way to hook into property get and set operations on an entity, and the proxies achieve this goal by overriding virtual members. Wenn wir also implizites Lazy Loading und eine effiziente Änderungs Nachverfolgung wünschen, müssen wir zu den poco-Klassendefinitionen zurückkehren und Eigenschaften als virtuell markieren.Thus, if we want to have implicit lazy loading and efficient change tracking we need to go back to our POCO class definitions and mark properties as virtual.

    public class Employee {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual DateTime HireDate { get; set; }
        public virtual ICollection<TimeCard> TimeCards { get; set; }
    }

Wir können weiterhin sagen, dass die Mitarbeiter Entität größtenteils persistent ist.We can still say the Employee entity is mostly persistence ignorant. Die einzige Anforderung besteht darin, virtuelle Member zu verwenden, und dies hat keine Auswirkung auf die Testability des Codes.The only requirement is to use virtual members and this does not impact the testability of the code. Wir müssen nicht von einer speziellen Basisklasse ableiten oder sogar eine spezielle Sammlung verwenden, die für die Lazy Loading reserviert ist.We don’t need to derive from any special base class, or even use a special collection dedicated to lazy loading. Wie der Code zeigt, steht jede Klasse, die ICollection T implementiert, < > zum Speichern verwandter Entitäten zur Verfügung.As the code demonstrates, any class implementing ICollection<T> is available to hold related entities.

Es gibt auch eine geringfügige Änderung, die wir in unserer Arbeitseinheit vornehmen müssen.There is also one minor change we need to make inside our unit of work. Lazy Load ist Standard mäßig deaktiviert, wenn Sie direkt mit einem ObjectContext-Objekt arbeiten.Lazy loading is off by default when working directly with an ObjectContext object. Es gibt eine Eigenschaft, die für die ContextOptions-Eigenschaft festgelegt werden kann, um verzögertes Laden zu ermöglichen, und wir können diese Eigenschaft in unserer echten Arbeitseinheit festlegen, wenn wir Lazy Loading überall aktivieren möchten.There is a property we can set on the ContextOptions property to enable deferred loading, and we can set this property inside our real unit of work if we want to enable lazy loading everywhere.

    public class SqlUnitOfWork : IUnitOfWork {
         public SqlUnitOfWork() {
             // ...
             _context = new ObjectContext(connectionString);
             _context.ContextOptions.LazyLoadingEnabled = true;
         }    
         // ...
     }

Wenn implizites Lazy Loading aktiviert ist, kann der Anwendungscode einen Mitarbeiter und die zugeordneten Zeitkarten des Mitarbeiters verwenden, während der verbleibende Arbeitsaufwand, den EF zum Laden zusätzlicher Daten benötigt, nicht vollständig unbekannt ist.With implicit lazy loading enabled, application code can use an employee and the employee’s associated time cards while remaining blissfully unaware of the work required for EF to load the extra data.

    var employee = _unitOfWork.Employees
                              .Single(e => e.Id == id);
    foreach (var card in employee.TimeCards) {
        // ...
    }

Lazy Load vereinfacht das Schreiben des Anwendungs Codes, und mit dem Proxy Magic bleibt der Code vollständig prüfbar.Lazy loading makes the application code easier to write, and with the proxy magic the code remains completely testable. In-Memory-Fakes der Arbeitseinheit können bei Bedarf einfach gefälschte Entitäten mit zugeordneten Daten vorab geladen werden.In-memory fakes of the unit of work can simply preload fake entities with associated data when needed during a test.

An dieser Stelle wird das Entwickeln von Depots mithilfe von IObjectSet < T und das unter > Suchen von Abstraktionen zum Ausblenden aller Vorzeichen des persistenzframe Works berücksichtigt.At this point we’ll turn our attention from building repositories using IObjectSet<T> and look at abstractions to hide all signs of the persistence framework.

Benutzerdefinierte DepotsCustom Repositories

Als wir zuerst das Entwurfsmuster für Arbeitseinheiten in diesem Artikel vorgestellt haben, haben wir einen Beispielcode dafür bereitgestellt, wie die Arbeitseinheit aussehen könnte.When we first presented the unit of work design pattern in this article we provided some sample code for what the unit of work might look like. Wir stellen diese ursprüngliche Idee mit dem Zeitkarten Szenario für Mitarbeiter und Mitarbeiter wieder her, mit dem wir gearbeitet haben.Let’s re-present this original idea using the employee and employee time card scenario we’ve been working with.

    public interface IUnitOfWork {
        IRepository<Employee> Employees { get; }
        IRepository<TimeCard> TimeCards { get;  }
        void Commit();
    }

Der Hauptunterschied zwischen dieser Arbeitseinheit und der Arbeitseinheit, die wir im letzten Abschnitt erstellt haben, besteht darin, wie diese Arbeitseinheit keine Abstraktionen aus EF4 Framework verwendet (es gibt kein IObjectSet < T > ).The primary difference between this unit of work and the unit of work we created in the last section is how this unit of work does not use any abstractions from the EF4 framework (there is no IObjectSet<T>). IObjectSet < T > funktioniert gut als Repository-Schnittstelle, aber die API, die Sie verfügbar macht, ist möglicherweise nicht perfekt an die Anforderungen unserer Anwendung angepasst.IObjectSet<T> works well as a repository interface, but the API it exposes might not perfectly align with our application’s needs. In diesem bevorstehenden Ansatz stellen wir die Repository mithilfe einer benutzerdefinierten IRepository < T- > Abstraktion dar.In this upcoming approach we will represent repositories using a custom IRepository<T> abstraction.

Viele Entwickler, die sich auf Test gesteuerte Entwurfs-, Verhaltens gesteuerte Entwurfs-und Domänen gesteuerte Methoden entwickeln, bevorzugen den IRepository < T- > Ansatz aus verschiedenen Gründen.Many developers who follow test-driven design, behavior-driven design, and domain driven methodologies design prefer the IRepository<T> approach for several reasons. Zuerst stellt die IRepository < T- > Schnittstelle eine "antibeschädigungsebene" dar.First, the IRepository<T> interface represents an “anti-corruption” layer. Wie von Eric Evans in seinem domänengesteuerten Entwurfs Buch beschrieben, behält eine antibeschädigungs Schicht Ihren Domänen Code von Infrastruktur-APIs, wie z. b. eine persistenzapi, fernAs described by Eric Evans in his Domain Driven Design book an anti-corruption layer keeps your domain code away from infrastructure APIs, like a persistence API. Zweitens können Entwickler Methoden im Repository erstellen, die genau den Anforderungen einer Anwendung entsprechen (wie beim Schreiben von Tests erkannt).Secondly, developers can build methods into the repository that meet the exact needs of an application (as discovered while writing tests). Beispielsweise kann es vorkommen, dass wir häufig eine einzelne Entität mit einem ID-Wert suchen müssen, damit wir der Repository-Schnittstelle eine findByID-Methode hinzufügen können.For example, we might frequently need to locate a single entity using an ID value, so we can add a FindById method to the repository interface.Die Definition von IRepository < T > sieht wie folgt aus:  Our IRepository<T> definition will look like the following.

    public interface IRepository<T>
                    where T : class, IEntity {
        IQueryable<T> FindAll();
        IQueryable<T> FindWhere(Expression<Func\<T, bool>> predicate);
        T FindById(int id);       
        void Add(T newEntity);
        void Remove(T entity);
    }

Beachten Sie, dass Sie eine iquerable T- < > Schnittstelle verwenden, um Entitäts Sammlungen verfügbar zu machen.Notice we’ll drop back to using an IQueryable<T> interface to expose entity collections. Iquervable < T > ermöglicht, dass LINQ-Ausdrucks Baumstrukturen in den EF4-Anbieter fließen und dem Anbieter eine ganzheitliche Ansicht der Abfrage zur Verfügung steht.IQueryable<T> allows LINQ expression trees to flow into the EF4 provider and give the provider a holistic view of the query. Eine zweite Option wäre die Rückgabe von IEnumerable < T > . Dies bedeutet, dass der EF4 LINQ-Anbieter nur die im Repository erstellten Ausdrücke sehen kann.A second option would be to return IEnumerable<T>, which means the EF4 LINQ provider will only see the expressions built inside of the repository. Alle Gruppierungen, Reihenfolge und Projektionen, die außerhalb des Repository durchgeführt werden, werden nicht in den SQL-Befehl zusammengefasst, der an die Datenbank gesendet wird. Dies kann die Leistung beeinträchtigen.Any grouping, ordering, and projection done outside of the repository will not be composed into the SQL command sent to the database, which can hurt performance. Andererseits überrascht ein Repository, das nur IEnumerable T- < > Ergebnisse zurückgibt, nie einen neuen SQL-Befehl.On the other hand, a repository returning only IEnumerable<T> results will never surprise you with a new SQL command. Beide Ansätze funktionieren, und beide Ansätze bleiben prüfbar.Both approaches will work, and both approaches remain testable.

Es ist einfach, eine einzige Implementierung der IRepository T- < > Schnittstelle mithilfe von Generika und der EF4 ObjectContext-API bereitzustellen.It’s straightforward to provide a single implementation of the IRepository<T> interface using generics and the EF4 ObjectContext API.

    public class SqlRepository<T> : IRepository<T>
                                    where T : class, IEntity {
        public SqlRepository(ObjectContext context) {
            _objectSet = context.CreateObjectSet<T>();
        }
        public IQueryable<T> FindAll() {
            return _objectSet;
        }
        public IQueryable<T> FindWhere(
                               Expression<Func\<T, bool>> predicate) {
            return _objectSet.Where(predicate);
        }
        public T FindById(int id) {
            return _objectSet.Single(o => o.Id == id);
        }
        public void Add(T newEntity) {
            _objectSet.AddObject(newEntity);
        }
        public void Remove(T entity) {
            _objectSet.DeleteObject(entity);
        }
        protected ObjectSet<T> _objectSet;
    }

Der IRepository < T- > Ansatz bietet uns eine zusätzliche Kontrolle über unsere Abfragen, da ein Client eine Methode aufrufen muss, um eine Entität zu erhalten.The IRepository<T> approach gives us some additional control over our queries because a client has to invoke a method to get to an entity. In der-Methode könnten wir zusätzliche Überprüfungen und LINQ-Operatoren zur Durchsetzung von Anwendungs Einschränkungen bereitstellen.Inside the method we could provide additional checks and LINQ operators to enforce application constraints. Beachten Sie, dass die Schnittstelle über zwei Einschränkungen für den generischen Typparameter verfügt.Notice the interface has two constraints on the generic type parameter. Die erste Einschränkung ist die von ObjectSet T benötigte Klasse Cons < > , und die zweite Einschränkung zwingt unsere Entitäten, IEntity – eine Abstraktion zu implementieren, die für die Anwendung erstellt wurde.The first constraint is the class cons taint required by ObjectSet<T>, and the second constraint forces our entities to implement IEntity – an abstraction created for the application. Die IEntity-Schnittstelle erzwingt Entitäten, dass eine lesbare ID-Eigenschaft vorhanden ist, und wir können diese Eigenschaft dann in der findByID-Methode verwenden.The IEntity interface forces entities to have a readable Id property, and we can then use this property in the FindById method. IEntity ist mit folgendem Code definiert.IEntity is defined with the following code.

    public interface IEntity {
        int Id { get; }
    }

IEntity könnte als geringfügige Verletzung der Persistenz bei der Persistenz angesehen werden, da unsere Entitäten zum Implementieren dieser Schnittstelle erforderlich sind.IEntity could be considered a small violation of persistence ignorance since our entities are required to implement this interface. Denken Sie daran, dass Persistenz bei der Persistenz von Kompromisse liegt, und für viele der findByID-Funktionen wird die von der-Schnittstelle festgelegten EinschränkungRemember persistence ignorance is about tradeoffs, and for many the FindById functionality will outweigh the constraint imposed by the interface. Die-Schnittstelle hat keine Auswirkung auf die Testability.The interface has no impact on testability.

Zum Instanziieren eines Live IRepository < T > ist ein EF4 ObjectContext erforderlich, sodass die Instanziierung durch eine konkrete Arbeitseinheits Implementierung verwaltet werden muss.Instantiating a live IRepository<T> requires an EF4 ObjectContext, so a concrete unit of work implementation should manage the instantiation.

    public class SqlUnitOfWork : IUnitOfWork {
        public SqlUnitOfWork() {
            var connectionString =
                ConfigurationManager
                    .ConnectionStrings[ConnectionStringName]
                    .ConnectionString;

            _context = new ObjectContext(connectionString);
            _context.ContextOptions.LazyLoadingEnabled = true;
        }

        public IRepository<Employee> Employees {
            get {
                if (_employees == null) {
                    _employees = new SqlRepository<Employee>(_context);
                }
                return _employees;
            }
        }

        public IRepository<TimeCard> TimeCards {
            get {
                if (_timeCards == null) {
                    _timeCards = new SqlRepository<TimeCard>(_context);
                }
                return _timeCards;
            }
        }

        public void Commit() {
            _context.SaveChanges();
        }

        SqlRepository<Employee> _employees = null;
        SqlRepository<TimeCard> _timeCards = null;
        readonly ObjectContext _context;
        const string ConnectionStringName = "EmployeeDataModelContainer";
    }

Verwenden des benutzerdefinierten RepositoryUsing the Custom Repository

Die Verwendung des benutzerdefinierten Repository unterscheidet sich nicht wesentlich von der Verwendung des Repository auf der Grundlage von IObjectSet < T > .Using our custom repository is not significantly different from using the repository based on IObjectSet<T>. Anstatt LINQ-Operatoren direkt auf eine Eigenschaft anzuwenden, müssen wir zuerst eine der Repository-Methoden aufrufen, um einen iquerable T-Verweis zu erfassen < > .Instead of applying LINQ operators directly to a property, we’ll first need to invoke one the repository’s methods to grab an IQueryable<T> reference.

    public ViewResult Index() {
        var model = _repository.FindAll()
                               .Include("TimeCards")
                               .OrderBy(e => e.HireDate);
        return View(model);
    }

Beachten Sie, dass der zuvor implementierte benutzerdefinierte Include-Operator unverändert funktioniert.Notice the custom Include operator we implemented previously will work without change. Die findByID-Methode des Repository entfernt duplizierte Logik von Aktionen, die versuchen, eine einzelne Entität abzurufen.The repository’s FindById method removes duplicated logic from actions trying to retrieve a single entity.

    public ViewResult Details(int id) {
        var model = _repository.FindById(id);
        return View(model);
    }

Es gibt keinen signifikanten Unterschied bei der Testability der beiden Ansätze, die wir untersucht haben.There is no significant difference in the testability of the two approaches we’ve examined. Wir könnten gefälschte Implementierungen von IRepository T bereitstellen, indem wir konkrete Klassen erstellen, die < > von HashSet Employee unterstützt werden, < > genau wie im letzten Abschnitt.We could provide fake implementations of IRepository<T> by building concrete classes backed by HashSet<Employee> - just like what we did in the last section. Allerdings bevorzugen einige Entwickler die Verwendung von Mockobjekten und Pseudo Objekt-Frameworks, anstatt Fakes zu entwickeln.However, some developers prefer to use mock objects and mock object frameworks instead of building fakes. Wir werden uns mit der Verwendung von Mock beschäftigen, um unsere Implementierung zu testen und die Unterschiede zwischen den Pseudo-und Fakes im nächsten Abschnitt zu erörtern.We’ll look at using mocks to test our implementation and discuss the differences between mocks and fakes in the next section.

Testen mit MockTesting with Mocks

Es gibt verschiedene Ansätze zum Entwickeln von Martin Fowler, der einen "Test Double" aufruft.There are different approaches to building what Martin Fowler calls a “test double”. Ein Test Double (z. b. ein Movie Stuntdouble) ist ein Objekt, das Sie für die "Position" bei echten Produktions Objekten während der Tests erstellen.A test double (like a movie stunt double) is an object you build to “stand in” for real, production objects during tests. Bei den in-Memory-Depots, die wir erstellt haben, handelt es sich um Test Doubles für die Depots, die SQL ServerThe in-memory repositories we created are test doubles for the repositories that talk to SQL Server. Wir haben gesehen, wie diese Test-Doubles während der Komponententests verwendet werden, um den Code zu isolieren und die Tests schnell ausführen zu lassen.We’ve seen how to use these test-doubles during the unit tests to isolate code and keep tests running fast.

Die Test Doubles, die wir erstellt haben, verfügen über echte, funktionierende Implementierungen.The test doubles we’ve built have real, working implementations. Im Hintergrund speichert jedes eine konkrete Auflistung von Objekten, und Sie fügen Objekte aus dieser Auflistung hinzu und entfernen Sie, während das Repository während eines Tests bearbeitet wird.Behind the scenes each one stores a concrete collection of objects, and they will add and remove objects from this collection as we manipulate the repository during a test. Einige Entwickler, die Ihre Tests erstellen möchten, verdoppeln diese Art – mit echtem Code und funktionierenden Implementierungen.Some developers like to build their test doubles this way – with real code and working implementations.Diese Test Doubles werden als Fakesbezeichnet.  These test doubles are what we call fakes. Sie verfügen über funktionierende Implementierungen, sind aber nicht für die Verwendung in der Produktion ausreichend.They have working implementations, but they aren’t real enough for production use. Das Fake-Repository schreibt tatsächlich nicht in die Datenbank.The fake repository doesn’t actually write to the database. Der gefälschte SMTP-Server sendet tatsächlich keine e-Mail-Nachricht über das Netzwerk.The fake SMTP server doesn’t actually send an email message over the network.

Pseudo-und FakesMocks versus Fakes

Es gibt einen anderen Typ von Test Double, der als Mockbezeichnet wird.There is another type of test double known as a mock. Obwohl Fakes über funktionierende Implementierungen verfügen, gibt es keine Implementierung.While fakes have working implementations, mocks come with no implementation. Mithilfe eines Pseudo Objekt Frameworks erstellen wir diese Mockobjekte zur Laufzeit und verwenden Sie als Test Doubles.With the help of a mock object framework we construct these mock objects at run time and use them as test doubles. In diesem Abschnitt verwenden wir das Open Source-Framework für das Microsoft-Framework.In this section we’ll be using the open source mocking framework Moq. Im folgenden finden Sie ein einfaches Beispiel für die dynamische Erstellung eines Test Double für ein Employee-Repository mithilfe von "muq".Here is a simple example of using Moq to dynamically create a test double for an employee repository.

    Mock<IRepository<Employee>> mock =
        new Mock<IRepository<Employee>>();
    IRepository<Employee> repository = mock.Object;
    repository.Add(new Employee());
    var employee = repository.FindById(1);

Wir stellen für eine IRepository-Mitarbeiter Implementierung eine Microsoft-Implementierung von muq vor < > und erstellen dynamisch eine.We ask Moq for an IRepository<Employee> implementation and it builds one dynamically. Wir können zu dem Objekt gelangen, das IRepository Employee implementiert, indem wir auf < > die Object-Eigenschaft des Mock < T-Objekts zugreifen > .We can get to the object implementing IRepository<Employee> by accessing the Object property of the Mock<T> object. Es ist das innere Objekt, das wir an unsere Controller übergeben können, und Sie wissen nicht, ob es sich um ein Test Double oder das echte Repository handelt.It is this inner object we can pass into our controllers, and they won’t know if this is a test double or the real repository. Wir können Methoden für das Objekt so aufrufen, wie Methoden für ein Objekt mit einer echten Implementierung aufgerufen werden.We can invoke methods on the object just like we would invoke methods on an object with a real implementation.

Beim Aufrufen der Add-Methode müssen Sie sich Fragen, was das mockrepository tun wird.You must be wondering what the mock repository will do when we invoke the Add method. Da keine Implementierung hinter dem Mock-Objekt vorhanden ist, wird von Add nichts unterstützt.Since there is no implementation behind the mock object, Add does nothing. Es gibt keine konkrete Sammlung im Hintergrund, wie wir es mit den von uns geschriebenen Fakes getan haben, sodass der Mitarbeiter verworfen wird.There is no concrete collection behind the scenes like we had with the fakes we wrote, so the employee is discarded. Wie sieht es mit dem Rückgabewert von "findByID" aus?What about the return value of FindById? In diesem Fall übernimmt das Mock-Objekt das einzige, was es tun kann. es wird ein Standardwert zurückgegeben.In this case the mock object does the only thing it can do, which is return a default value. Da wir einen Verweistyp (einen Mitarbeiter) zurückgeben, ist der Rückgabewert ein NULL-Wert.Since we are returning a reference type (an Employee), the return value is a null value.

Mock klingen möglicherweise wertlos. Allerdings gibt es noch zwei weitere Features, über die wir nicht gesprochen haben.Mocks might sound worthless; however, there are two more features of mocks we haven’t talked about. Zuerst zeichnet das MOQ-Framework alle Aufrufe auf dem Mock-Objekt auf.First, the Moq framework records all the calls made on the mock object. Zu einem späteren Zeitpunkt im Code können Sie die Frage stellen, ob jemand die Add-Methode aufgerufen hat, oder ob jemand die findByID-Methode aufgerufen hat.Later in the code we can ask Moq if anyone invoked the Add method, or if anyone invoked the FindById method. Wir werden später sehen, wie diese "Black Box"-Aufzeichnungsfunktion in Tests verwendet werden kann.We’ll see later how we can use this “black box” recording feature in tests.

Das zweite hervorragend ist, wie wir MOQ verwenden können, um ein Mock-Objekt mit Erwartungenzu programmieren.The second great feature is how we can use Moq to program a mock object with expectations. Eine Erwartungs Angabe weist das Mock-Objekt an, wie auf eine bestimmte Interaktion reagiert werden soll.An expectation tells the mock object how to respond to any given interaction. Beispielsweise können wir eine Erwartungs Annahme in unser Mock programmieren und Sie darüber informieren, dass ein Employee-Objekt zurückgegeben wird, wenn ein Benutzer "findByID" aufruftFor example, we can program an expectation into our mock and tell it to return an employee object when someone invokes FindById. Das-Framework verwendet eine Setup-API und Lambda-Ausdrücke, um diese Erwartungen zu programmieren.The Moq framework uses a Setup API and lambda expressions to program these expectations.

    [TestMethod]
    public void MockSample() {
        Mock<IRepository<Employee>> mock =
            new Mock<IRepository<Employee>>();
        mock.Setup(m => m.FindById(5))
            .Returns(new Employee {Id = 5});
        IRepository<Employee> repository = mock.Object;
        var employee = repository.FindById(5);
        Assert.IsTrue(employee.Id == 5);
    }

In diesem Beispiel stellen wir fest, dass Sie ein Repository dynamisch erstellen und dann das Repository mit einer Erwartung programmieren.In this sample we ask Moq to dynamically build a repository, and then we program the repository with an expectation. Die Erwartungs Angabe weist das Mock-Objekt an, ein neues Employee-Objekt mit dem ID-Wert 5 zurückzugeben, wenn jemand die findByID-Methode aufruft und den Wert 5 übergibt.The expectation tells the mock object to return a new employee object with an Id value of 5 when someone invokes the FindById method passing a value of 5. Dieser Test wird erfolgreich durchlaufen, und es ist nicht erforderlich, eine vollständige Implementierung für das gefälschte IRepository t zu erstellen < > .This test passes, and we didn’t need to build a full implementation to fake IRepository<T>.

Schauen wir uns noch einmal die Tests an, die wir zuvor geschrieben haben, und arbeiten Sie zur Verwendung von Pseudo-anstelle von Fakes.Let’s revisit the tests we wrote earlier and rework them to use mocks instead of fakes. Wie zuvor verwenden wir eine Basisklasse, um die gemeinsamen Teile der Infrastruktur einzurichten, die wir für alle Tests des Controllers benötigen.Just like before, we’ll use a base class to setup the common pieces of infrastructure we need for all of the controller’s tests.

    public class EmployeeControllerTestBase {
        public EmployeeControllerTestBase() {
            _employeeData = EmployeeObjectMother.CreateEmployees()
                                                .AsQueryable();
            _repository = new Mock<IRepository<Employee>>();
            _unitOfWork = new Mock<IUnitOfWork>();
            _unitOfWork.Setup(u => u.Employees)
                       .Returns(_repository.Object);
            _controller = new EmployeeController(_unitOfWork.Object);
        }

        protected IQueryable<Employee> _employeeData;
        protected Mock<IUnitOfWork> _unitOfWork;
        protected EmployeeController _controller;
        protected Mock<IRepository<Employee>> _repository;
    }

Der Setup Code bleibt größtenteils unverändert.The setup code remains mostly the same. Anstatt Fakes zu verwenden, verwenden wir MOQ, um Mock-Objekte zu erstellen.Instead of using fakes, we’ll use Moq to construct mock objects. Die-Basisklasse ordnet an, dass die Mock-Arbeitseinheit ein Mock-Repository zurückgibt, wenn der Code die Employees-Eigenschaft aufruft.The base class arranges for the mock unit of work to return a mock repository when code invokes the Employees property. Der Rest des Mock-Setups erfolgt in den Test Vorrichtungen, die für jedes bestimmte Szenario vorgesehen sind.The rest of the mock setup will take place inside the test fixtures dedicated to each specific scenario. Beispielsweise wird durch die Test Fixierung für die Index Aktion das mockrepository so eingerichtet, dass eine Liste von Mitarbeitern zurückgegeben wird, wenn die Aktion die FindAll-Methode des mockrepository aufruft.For example, the test fixture for the Index action will setup the mock repository to return a list of employees when the action invokes the FindAll method of the mock repository.

    [TestClass]
    public class EmployeeControllerIndexActionTests
               : EmployeeControllerTestBase {
        public EmployeeControllerIndexActionTests() {
            _repository.Setup(r => r.FindAll())
                        .Returns(_employeeData);
        }
        // .. tests
        [TestMethod]
        public void ShouldBuildModelWithAllEmployees() {
            var result = _controller.Index();
            var model = result.ViewData.Model
                          as IEnumerable<Employee>;
            Assert.IsTrue(model.Count() == _employeeData.Count());
        }
        // .. and more tests
    }

Mit Ausnahme der Erwartungen sehen unsere Tests den Tests ähnlich wie zuvor.Except for the expectations, our tests look similar to the tests we had before. Mit der Aufzeichnungs Fähigkeit eines Mock-Frameworks können wir die Tests jedoch in einem anderen Winkel durchgehen.However, with the recording ability of a mock framework we can approach testing from a different angle. Wir betrachten diese neue Perspektive im nächsten Abschnitt.We’ll look at this new perspective in the next section.

Testzustand im Vergleich zu InteraktionState versus Interaction Testing

Es gibt verschiedene Techniken, die Sie zum Testen von Software mit Mock-Objekten verwenden können.There are different techniques you can use to test software with mock objects. Ein Ansatz besteht in der Verwendung von Zustands basierten Tests, was in diesem Artikel bisher geschehen ist.One approach is to use state based testing, which is what we have done in this paper so far. Zustands basierte Tests machen Assertionen zum Zustand der Software.State based testing makes assertions about the state of the software. Im letzten Test haben wir eine Aktionsmethode auf dem Controller aufgerufen und eine Aussage über das Modell vorgenommen, das erstellt werden soll.In the last test we invoked an action method on the controller and made an assertion about the model it should build. Hier sind einige weitere Beispiele für den Test Status:Here are some other examples of testing state:

  • Vergewissern Sie sich, dass das Repository das neue Mitarbeiter Objekt enthält, nachdem Create ausgeführt wurde.Verify the repository contains the new employee object after Create executes.
  • Vergewissern Sie sich, dass das Modell nach der Ausführung des Indexes eine Liste aller Mitarbeiter enthält.Verify the model holds a list of all employees after Index executes.
  • Vergewissern Sie sich, dass das Repository nach der Ausführung von DELETE keinen bestimmten Mitarbeiter enthält.Verify the repository does not contain a given employee after Delete executes.

Ein weiterer Ansatz, den Sie mit Mock-Objekten erkennen, ist das Überprüfen von Interaktionen.Another approach you’ll see with mock objects is to verify interactions. Während Zustands basierte Tests Assertionen zum Status von Objekten machen, werden durch Interaktions basierte Tests Assertionen für die Interaktion von Objekten durchführt.While state based testing makes assertions about the state of objects, interaction based testing makes assertions about how objects interact. Beispiel:For example:

  • Vergewissern Sie sich, dass der Controller beim Ausführen von CREATE die Add-Methode des Repository aufruft.Verify the controller invokes the repository’s Add method when Create executes.
  • Vergewissern Sie sich, dass der Controller die FindAll-Methode des Repository aufruft, wenn Index ausgeführt wirdVerify the controller invokes the repository’s FindAll method when Index executes.
  • Vergewissern Sie sich, dass der Controller die Commit-Methode der Arbeitseinheit aufruft, um beim Ausführen der Bearbeitung Änderungen zu speichern.Verify the controller invokes the unit of work’s Commit method to save changes when Edit executes.

Interaktions Tests erfordern häufig weniger Testdaten, da wir nicht innerhalb von Auflistungen stehen und die Anzahl überprüfen.Interaction testing often requires less test data, because we aren’t poking inside of collections and verifying counts. Wenn wir z. b. wissen, dass die Aktion "Details" die findByID-Methode eines Repository mit dem korrekten Wert aufruft, verhält sich die Aktion wahrscheinlich ordnungsgemäß.For example, if we know the Details action invokes a repository’s FindById method with the correct value - then the action is probably behaving correctly. Wir können dieses Verhalten überprüfen, ohne Testdaten einzurichten, die von findByID zurückgegeben werden sollen.We can verify this behavior without setting up any test data to return from FindById.

    [TestClass]
    public class EmployeeControllerDetailsActionTests
               : EmployeeControllerTestBase {
         // ...
        [TestMethod]
        public void ShouldInvokeRepositoryToFindEmployee() {
            var result = _controller.Details(_detailsId);
            _repository.Verify(r => r.FindById(_detailsId));
        }
        int _detailsId = 1;
    }

Das einzige Setup, das in der obigen Test Fixierung erforderlich ist, ist das von der Basisklasse bereitgestellte Setup.The only setup required in the above test fixture is the setup provided by the base class. Wenn wir die Controller Aktion aufrufen, zeichnet MOQ die Interaktionen mit dem Mock-Repository auf.When we invoke the controller action, Moq will record the interactions with the mock repository. Mithilfe der Verify-API von WQ können wir die Frage stellen, ob der Controller "findByID" mit dem richtigen ID-Wert aufgerufen hat.Using the Verify API of Moq, we can ask Moq if the controller invoked FindById with the proper ID value. Wenn der Controller die Methode nicht aufgerufen hat oder die Methode mit einem unerwarteten Parameterwert aufgerufen hat, löst die Verify-Methode eine Ausnahme aus, und der Test schlägt fehl.If the controller did not invoke the method, or invoked the method with an unexpected parameter value, the Verify method will throw an exception and the test will fail.

Es folgt ein weiteres Beispiel, um zu überprüfen, ob die CREATE-Aktion Commit für die aktuelle Arbeitseinheit aufruft.Here is another example to verify the Create action invokes Commit on the current unit of work.

    [TestMethod]
    public void ShouldCommitUnitOfWork() {
        _controller.Create(_newEmployee);
        _unitOfWork.Verify(u => u.Commit());
    }

Eine Gefahr beim Testen der Interaktion ist die Tendenz, Interaktionen zu überschreiten.One danger with interaction testing is the tendency to over specify interactions. Die Fähigkeit des Mockobjekts, jede Interaktion mit dem Mock-Objekt aufzuzeichnen und zu überprüfen, bedeutet nicht, dass der Test versuchen soll, jede Interaktion zu überprüfen.The ability of the mock object to record and verify every interaction with the mock object doesn’t mean the test should try to verify every interaction. Einige Interaktionen sind Implementierungsdetails, und Sie sollten nur die Interaktionen überprüfen, die zum erfüllen des aktuellen Tests erforderlich sind.Some interactions are implementation details and you should only verify the interactions required to satisfy the current test.

Die Wahl zwischen den Mocken oder Fakes hängt größtenteils von dem System ab, das Sie testen, und Ihren persönlichen (oder Team-) Voreinstellungen.The choice between mocks or fakes largely depends on the system you are testing and your personal (or team) preferences. Mock-Objekte können die Menge an Code, den Sie zum Implementieren von Test Doubles benötigen, drastisch reduzieren, aber nicht jeder ist für die Programmierung von Erwartungen und das Überprüfen von InteraktionenMock objects can drastically reduce the amount of code you need to implement test doubles, but not everyone is comfortable programming expectations and verifying interactions.

ZusammenfassungConclusions

In diesem Whitepaper haben wir verschiedene Ansätze zum Erstellen von testbarem Code gezeigt, während wir die ADO.NET-Entity Framework für die Daten Persistenz verwenden.In this paper we’ve demonstrated several approaches to creating testable code while using the ADO.NET Entity Framework for data persistence. Wir können integrierte Abstraktionen wie IObjectSet t nutzen < > oder eigene Abstraktionen wie IRepository < t erstellen > .We can leverage built in abstractions like IObjectSet<T>, or create our own abstractions like IRepository<T>.In beiden Fällen ermöglicht die poco-Unterstützung im ADO.NET-Entity Framework 4,0, dass die Consumer dieser Abstraktionen permanent ignoriert werden und hochgradig testfähig bleiben.  In both cases, the POCO support in the ADO.NET Entity Framework 4.0 allows the consumers of these abstractions to remain persistent ignorant and highly testable. Zusätzliche EF4-Features wie implizites Lazy Loading ermöglichen, dass Geschäfts-und Anwendungs Dienst Code funktionieren, ohne sich Gedanken über die Details eines relationalen Datenspeicher zu machen.Additional EF4 features like implicit lazy loading allows business and application service code to work without worrying about the details of a relational data store. Schließlich können die von uns erstellten Abstraktionen leicht in Komponententests hinein oder gefälscht werden, und wir können diese Test Doubles verwenden, um schnelle, hochgradig isolierte und zuverlässige Tests zu erzielen.Finally, the abstractions we create are easy to mock or fake inside of unit tests, and we can use these test doubles to achieve fast running, highly isolated, and reliable tests.

Weitere RessourcenAdditional Resources

BiografieBiography

Scott allen ist Mitglied des technischen Personals bei Pluralsight und der Gründer von OdeToCode.com.Scott Allen is a member of the technical staff at Pluralsight and the founder of OdeToCode.com. In 15 Jahren kommerzieller Softwareentwicklung hat Scott an Lösungen für alles von 8-Bit Embedded-Geräten bis hin zu hochgradig skalierbaren ASP.NET-Webanwendungen gearbeitet.In 15 years of commercial software development, Scott has worked on solutions for everything from 8-bit embedded devices to highly scalable ASP.NET web applications. Scott finden Sie in seinem Blog unter "odeycode" oder auf Twitter unter https://twitter.com/OdeToCode .You can reach Scott on his blog at OdeToCode, or on Twitter at https://twitter.com/OdeToCode.