MVC zum Testen
Erstellen testbarer ASP.NET MVC-Anwendungen
Justin Etheredge


Themen in diesem Artikel:

  • XML-Webdienste MVC
  • Erstellen von Lösungen getestet
  • Abhängigkeitsinjektion
In diesem Artikel werden die folgenden Technologien verwendet:
ASP.NET
Sie haben wahrscheinlich die alte Sprichworts, "besser, schneller und billiger, Kommissionierung nur zwei." gehört Wenn Sie etwas gut und schnelle möchten, soll es ist nicht billig werden, und wenn Sie etwas schnell und kostengünstig möchten, soll es ist nicht sehr gut. Günstiger, schneller und besser bedeutet, die wir mehr Code schreiben schneller, rechts müssen? Wenn nur das einfache wurden. Lernen, schneller geben möglicherweise erfüllen zwei der Anforderungen, aber es ist nicht zu der Software, die Sie entwickeln eine besser wechseln. Wie verbessern wir Software? Was bedeutet "besser"bedeuten?
"Besser"bedeutet, dass erzeugen flexible, verwaltbarer Software mit einer niedrigen Anzahl Codefehler;bessere Software geht es um langfristige Wartbarkeit. Um dies zu erreichen, ist es eine wichtige Entwurfsentscheidung, sicherzustellen, dass die Komponenten lose verknüpft sind. Lose gekoppelte Software hat viele Vorteile. Eine abhebt, verbessert, dass unsere Möglichkeit, Lösungen zu testen ist. Wenn wir Software, die mühelos in kleine Teile unterteilt werden kann erstellen, wird es einfacher zu testen. Es klingt einfach, wenn Sie Sie word auf diese Weise, sondern die benötigte Software heute, ist schwierig zu testen oder verwalten angezeigt, die er nicht so einfach wie wir denken möchten möglicherweise verwendet. Software, nützliche Aktionen kombiniert werden muss, aber Entwickler benötigen Tools und Techniken zum Verringern der Kopplung, sodass Lösungen einfacher sind zu testen.


Testen Support in XML-Webdienste MVC
Wir sind etwas ein Renaissance in Tests Tools und Techniken sehen. Überall Sie suchen finden Sie Test-Frameworks und Tools wie mocking Container und Dependency Injection-Frameworks. Nicht alle diese toolsam Ende uns Anwendungen erstellen, die viel besser prüfbare ermöglichen, aber dient zum Testen.
Aus der Perspektive eines allgemeinen testbare Anwendung ist eine Anwendung, lose gekoppelte genug, um seine unabhängige Teile isoliert getestet werden können. Schreiben einer Anwendung, die getestet werden kann viel Entwurfsarbeit ab dem Anfang erfordert, aber testbare Anwendungen zu erstellen, Entwickler müssen auch verwenden, Tools und Frameworks, die um Paradigmen dienen, die leicht getestet werden. Wenn Sie nicht mit Tools, die testbaren Anwendungen ermöglichen arbeiten, wird kein Betrag guter Entwurf helfen. Glücklicherweise XML-Webdienste MVC wurde Einrichten von Grund auf neu entwickelt, getestet werden und mehrere wichtige Entwurfsentscheidungen wurden vorgenommen, damit Entwickler testbare Anwendungen erstellen.
Einige würden sagen, dass das Muster Model View Controller (MVC) grundsätzlich mehr als das Page Controller-Muster (das Muster XML-Webdienste Webformulare zumindest dort angestellt) testbar ist da diese Trennung von der Anwendungslogik Datenfluss und die Anzeige Logik fördert. In MVC-Muster befindet sich in die Controller-Klassen, die die Logik, die den Fluss von der Anwendung steuert, damit ein Entwickler problemlos instanziieren und diese Logik, Ausführen als handele es andere .NET-Klasse. Dadurch können Entwickler problemlos ausführen die Geschäftslogik verwenden einen einfachen Komponententest in ähnlich wie Sie andere POCO (einfache alte CLR Object)-Klasse testen möchten.
Eine andere Wahl das Produktentwicklungsteam bei Microsoft gestellt war, viele Teile des Frameworks austauschbare werden können. Zu wichtiger Teile gehören der Controller Factorys, die die Instanziierung von Klassen Controller gesteuert. Ermöglicht Entwicklern, ersetzen oder Erweitern der Controller Factorys ermöglicht die Logik ausführen, können zu beheben und einfügen die Abhängigkeiten der Domänencontroller. Ermöglicht die Abhängigkeiten eines Domänencontrollers zur Laufzeit eingefügt ist entscheidend für weitere lose gekoppelten und testbaren Anwendungen erstellen.
In herkömmlichen XML-Webdienste, eines Hindernisse, die während des Testens Entwickler stoßen ist der Fülle von statischen Klassen bei jeder Anforderung verwendet. XML-Webdienste MVC-Team versucht die Entscheidung, viele der statischen Hilfsfunktion .NET Klassen (z. B. HttpContext und HttpRequest) umbrochen, so dass Sie während der Tests mit einem Stub ersetzt werden können. XML-Webdienste MVC bietet viele Abstraktionen, die diese Klassen vermeiden Entwicklern dabei helfen, aber an Orten, in denen Sie deren Verwendung erforderlich sind, die Wrapper vereinfachen diesen Code testen.


Obwohl ASP.NET MVC viele der Tools Entwickler testbare Anwendungen erstellen müssen bietet, können nicht Sie darauf, die Sie in die richtige Richtung verlassen. Stattdessen müssen Sie Ihre Anwendungen absichtlich Tests entwerfen, und ein paar zusätzliche Tools helfen, mit diesem:
  • Einheit testen Framework. xUnit.NET von Brad Wilson und Jim Newkirk. xUnit bietet eine Möglichkeit, automatisierte Komponententests auszuführen. Viele Personen verwenden MSTest, aber viele andere Einheit testen Rahmenstrukturen sind draußen, und fangen Sie betrachten einige davon. Ich habe die xUnit.NET, da Sie einfach und problemlos erweitert, und eine sehr saubere Syntax hat. Ich verwende die xUnit-Tests Läufer für Resharper und die xUnit GUI Test Läufer in im Ordner der Beispielanwendung.
  • Dependency Injection-Framework. Ninject 2 von Nate Kohari. Ninject bietet eine Möglichkeit, Klassen in Ihrer Anwendung verbinden. Ich werde dies später in diesem Artikel ausführlicher beschreiben.
  • Framework imitieren. Moq durch Clarius Consulting. Moq bietet ein Framework für Schnittstellen und Klassen in der Testphase imitieren.


Anwendung Szenario
Zum Anzeigen wie testbare Anwendung entwerfen, werde ich eine einfache Anwendungsszenario verwenden, in der Liste der Kategorien und Produkte auf einer Webseite angezeigt werden. Die Anwendung umfasst auch einfache Bildschirme anzeigen, bearbeiten und Erstellen von Kategorien und Produkte. Das einfache Schema in Abbildung 1 gezeigte besteht eine 1: n-Zuordnung zwischen Kategorien und Produkte.

Abbildung 1 Schemas für die Beispielanwendung(Click the image for a larger view)


High-Level Anwendungsentwurf
Wenn Sie eine Anwendung erstellen, kann der ursprüngliche Entwurf eine lange Möglichkeit in Richtung helfen oder hindering langfristige Anwendungszustands wechseln – die Verwaltbarkeit und Testbarkeit. Der Ansatz in vielen Architekturen besteht darin, die optimale Zeitspanne Abstraktion zu finden, ohne viel Aufwand erstellen. Das MVC-Muster stellt bereits einige Architekturrichtlinien definiert drei Ebenen für eine Anwendung bereit. Einige Entwickler können sich vorstellen, dass diese drei Ebenen genügend Abstraktion große Anwendungen bereitstellen. Leider sind, ist häufig nicht der Fall, und wie Sie sehen werden, das Modell problemlos kann mehr als eine einzelne Ebene.
Beachten Sie Sie, dass in vielen kleinen Anwendungen, einschließlich der im Beispiel für diesen Artikel drei Abstraktionsebenen wahrscheinlich ausreichend ist. In diesen Fällen ist einigen Ansichten, Controller und eine Reihe von Klassen für die Interaktion mit wahrscheinlich ausreichend. Allerdings werden mehrere zusätzliche Ebenen der Abstraktion benötigt, um eine hochgradig testbare Anwendung erstellen. Viele Architekturmuster bei unserer Veräußerung können in den Gesamtentwurf, einschließlich das MVC-Muster zu bilden.
XML-Webdienste MVC Code-Behind-Seiten
If you were following ASP.NET MVC before the final release, you may have noticed that the code-behind pages have been removed from the framework. Diese Seiten bedient keine Funktionalität im XML-Webdienste MVC Framework und höher gestuft eingefügt Logik in Ansichten, wobei es nicht gehört. In den Ansichten enthaltenen Logik ist schwierig, im Wesentlichen die gleiche Weise testen, die Code-Behind-Dateien im XML-Webdienste Webformulare schwierig zu testen sind.
Die meisten Lesen dieses Artikels sind vermutlich vertraut, mit dem Muster, aber Sie können nicht über die Auswirkungen für die Anwendung Testbarkeit gedacht haben. Der erste Teil des MVC-Muster ist die Ansicht, die Logik für das Rendering Daten an einen Client enthält. Ursprünglich, dies war eine Benutzeroberfläche, aber ein Client kann einen Webbrowser, einem Webdienst, clientseitige JavaScript und usw. sein. Die Ansicht sollte nur zum Rendern von Daten für ein Consumer verwendet werden, und der Umfang erforderliche Logik sollte in Hilfsklassen so weit wie möglich abstrahiert.
Das Modell stellt Back-End einer Anwendung dar. In diesem Teil der Anwendung, die in der das Muster XML-Webdienste MVC-Implementierung sehr lose definiert ist kann auf vielen verschiedenen Formen, und es sehr wahrscheinlich hängt der Umfang der Anwendung. In einer kleinen Anwendung mit wenig komplexe Geschäftslogik beispielsweise, diese Schicht einfach sein eine Reihe von Business-Objekten mithilfe des Musters Active aufzeichnen, die Sie direkt in der Domänencontroller-Schicht mit interagieren.
Der Controller koordiniert den Ablauf der Anwendung durch Daten aus dem Modell und Übergabe an die gewünschte Ansicht. Da diese Klasse von der Anzeige Logik getrennt ist, sollte mithilfe von ein paar Techniken Sie möglicherweise zum Instanziieren dieser Klasse in einem Komponententest und weisen keine Abhängigkeiten von der ASP.NET-Laufzeit. Dies ermöglicht das vollständige Testen des Domänencontrollers ohne in einem Webserver ausgeführt.


Abstraktion des Modells
Beim Betrachten diese Schichten möglicherweise ein paar Orte angezeigt werden, wobei zusätzliche Abstraktionsschicht Testen eine Anwendung erleichtern würde. Dies ist häufig bei komplexeren Anwendungen, einem Bereich zwischen den Controller und das Modell. Wenn Sie die Anordnung weiter oben beschriebenen haben, könnte Ihre Domänencontroller Aktionen wie folgt aussehen:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formCollection)
{
    Category category = Category.FindById(id);
    category.Name = formCollection["name"];
    category.Save();            
    return RedirectToAction("List");
}
Hier die Herausforderungen sind mindestens hat zwei. Sie können nicht zunächst einfach dieser Code testen, ohne eine Datenbankverbindung. Da wir Active Datensatz Muster verwenden, ist unsere Datenzugriffs-Code für die Entität Domäne gekoppelt, damit wir ihn einfach austauschen kann nicht. Auch wenn Sie Geschäftslogik, die nicht vollständig in der Domäne Entität enthalten ist verfügen, konnte Sie leaking Geschäfts- oder Anwendungslogik in die Controllerklasse beginnen. Dies kann zu Duplizierung von Code oder sogar schlechter führen – Fehler Wortstamm von Inkonsistenzen bei der Implementierung. Dies kann Testbarkeit beeinträchtigen, da Sie jetzt sicher, dass Sie das gleiche Verhalten an mehreren Stellen innerhalb der Anwendung testen müssen.

Das Repository-Muster
Nächste Schritt in Verbessern der Testbarkeit dieses Entwurfs besteht darin, die Repository-Musters nutzen, die von Martin Fowler in seinem Buch Muster von Enterprise Application Architecture beschrieben wird. Repository-Musters ist häufig falsch interpretiert, als Ebene, die zwischen der Anwendung und einer Datenbank befindet, aber es ist tatsächlich eine Schicht, die zwischen der Anwendung und jede Art von permanenten Speicher befindet. Dieses Muster beschränkt die Datenzugriff auf ein Paar Schlüssel stellen, so dass Sie es testen Anwendungslogik in Isolation vereinfachen entfernen.
Implementierungen von Repository-Musters eine Ansicht von Ihren permanenten Speicher, die ein Repository im Speicher werden bereitstellen. Die Schnittstelle zu einem solchen Repository konnte ähnlich sein:
public interface ICategoryRepository
{
    IEnumerable<Category> FindAll();
    void Insert(Category category);
    Category FindById(int id);
    void Update(Category category);
}
Tests in Isolation
Some people might think that testing in isolation is not important and choose to only write integration tests. Testframework wie MSTest oder NUnit kann Integrationstests weiterhin ausgeführt, aber Sie mehreren Ebenen der Anwendung, einschließlich Aufrufe über das Netzwerk den Zugriff auf den Datenträger beibehalten von Daten an eine Datenbank testen und usw.. Diese Arten von Tests sind sehr wichtig und müssen in allen Anwendungen vorhanden sein, aber eine Anwendung wächst, diese Tests werden sehr langsam und können auf verschiedene Entwickler Computern zerbrechliche. Trennen von Komponententests und Integrationstests können Sie die Komponententests schnell und zuverlässig auf Entwickler Computern ausführen während der Ausführung der Integrationstests regelmäßig auf dem Server erstellen.
Wenn Sie beginnen, Klassen von einem anderen und Test in Isolation zu trennen, werden Sie feststellen, dass Tests wird viel weniger zerbrechliche und viel einfacher. Testen Sie die Rand Fällen der aufrufenden Klasse simulieren Sie eine Abhängigkeit, Sie steuern, genau, was er zurückgibt und Sie können leichter. Da sich auf mehreren Ebenen von Abhängigkeiten verlassen nicht sind, müssen Sie auch nicht so viel berücksichtigt beim Vorbereiten der Testszenarios für Ihre. Nur Bedenken Sie, dass Klassen in Isolation kein Test ist Ersatz für das Schreiben von Integrationstests, um sicherzustellen, dass alle Komponenten gut zusammenarbeiten.
Hier zu beachten ist es wichtig, dass verschieben Datenpersistenz außerhalb der Domäne Entität und in einer separaten Klasse, die Sie während der Tests leichter ersetzen können. Anwenden des Musters, der zuvor gezeigten Aktion bearbeiten sieht wie Abbildung 2 aus.

Das Repository einfügen
Sieht der Code im Abbildung 2 sauberen, aber wie ersetzen Sie die Repository-Klasse während der Tests? Ganz einfach. Anstelle der Instanziierung der Klasse innerhalb der Methode, übergeben Sie es in der Klasse. In diesem Fall können Sie es als Konstruktorparameter übergeben. Das Problem ist jedoch, dass Sie die Instanziierung der Controller Klassen steuern nicht. Um dieses Problem zu beheben, können Sie das Dependency Injection-Framework, das ich oben verwiesen. Zahlreiche Frameworks sind auf dem Markt verfügbar. Ich hatte Ninject 2 verwenden. Finden (Wenn Sie mit dem Dependency Injection vertraut sind, Sie im Artikel in der Ausgabe vom September 2005 des MSDN Magazin durch Greif Caprio, " Abhängigkeitsinjektion." Wie bereits erwähnt, ist die erste Schritt unter Dependency Injection die CategoryRepository der Controller über den Controller-Konstruktor übergeben:
public CategoryController(ICategoryRepository categoryRepository)
{
    this.categoryRespository = categoryRepository;
}

Abbildung 2 Nutzung im Repository

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formCollection)
{
    var categoryRepository = new CategoryRepository();
    Category category = categoryRepository.FindById(id);
    category.Name = formCollection["name"];
    categoryRepository.Update(category);

    return RedirectToAction("List");
}
Als Nächstes müssen Sie Ninject informieren, wenn es eine ICategoryService-Schnittstelle erkennt, eine Instanz der CategoryService einfügen muss. Jeder Dependency Injection-Framework verfügt über eine andere Methode der Konfiguration und Ninject eine codebasierten Konfiguration statt mehr üblichen XML verwendet. Da diese Bindung sehr einfach ist, ist der Code für die Implementierung nur eine Zeile:
Bind<ICategoryRepository>().To<CategoryRepository>().InTransientScope();
Diese Anweisung teilt das Dependency Injection-Framework ICategoryRepository an CategoryRepository in einem flüchtigen Bereich gebunden, d., jedes Mal ein ICategoryRepository angefordert wird h., Sie erhalten eine neue Instanz der CategoryRepository. Dies steht im Gegensatz zu etwa einen Singleton Bereich, in denen Sie weiterhin dieselbe Instanz im Repository wieder erhalten.
Mit dem Controller umgestaltet werden, sodass das Repository in durch den Konstruktor übergeben wird können Sie das Verhalten von dem Controller unabhängig von der Datenbank jetzt testen. Eine Methode zu diesem Zweck ist eine dummy Klasse (häufig als einen Stub bezeichnet), die die ICategoryRepository-Schnittstelle implementiert schreiben. Sie können dann die Methoden implementieren, die müssen Sie dummy Daten zurückgeben und dummy Operationen, obwohl dies wie viel Arbeit scheint. Was geschieht zusätzlich, wenn Sie mehr als ein Satz von unechten Daten für eine einzelne Methode in verschiedenen Tests zurückgeben? Mit mehrere Implementierungen der Stub oder Flags in der Stub landen würde, und, die möglicherweise eine beträchtliche Menge zusätzlichen Aufwand.
Hier ist ein gutes mocking Framework kommen kann. Ein mocking Framework ermöglicht Ihnen das mock-Objekte erstellen. Pseudoobjekte ermöglichen Entwicklern, emuliert das Verhalten von einer anderen Klasse während der Tests über Schnittstellen oder virtuelle Methoden und überprüfen Sie, dass die erwartete Ereignisse aufgetreten ist. Das Verhalten von Mockobjekten (auch einfach als Mocks bezeichnet) ist was ermöglicht das Ersetzen von Abhängigkeiten in den Klassen und Testen Sie diese Klassen ohne die echten Abhängigkeiten an Stelle.
Dies ist möglicherweise ein wenig verwirrend, aber Sie benötigen das mocking Framework einige stubbing führen können. Ja, diese Rechte ist. Da ein Mock ein Objekt, die bestätigen ist, dass eine bestimmte Aktion auf ausgeführt wurde und ein Stub unechten Daten oder Aktionen bietet ermöglicht, sollte es klar sein, dass in den meisten realen Szenarios eine Mischung aus beiden soll. Erhalten diese Tatsache, verfügen die meisten Mockframeworks Funktionen, die Ihnen ermöglicht, Methoden oder Eigenschaften stub ohne Assert Verhalten. (Wenn Sie noch ein wenig unsicher Mocks gegenüber Stubs sind, Martin Fowler einen guten Artikel besitzt, auf dem Thema " Mocks werden Stubs nicht."
Der Code im Abbildung 3 zeigt einen Test, die die ICategoryRepository-Schnittstelle mocks Stub die FindAll-Methode, sodass es eine dummy Liste von Kategorien gibt, und dann die Controllerklasse mocked Instanz übergibt. Die Liste-Methode für die Controllerklasse wird aufgerufen, und das Ergebnis kann bestätigt werden. In diesem Fall wird der Test Assert, dass das Modell ein IEnumerable von Kategorien und, dass in der Liste eine Kategorie ist.

Abbildung 3 Liste Aktion Testen mit Mocks

[Fact]
public void ListActionReturnsListOfCategories()
{
    // Arrange

    // create the mock
    var mockRepository = new Mock<ICategoryRepository>();

    // create a list of categories to return
    var categories = new[] { new Category {Id = 1, Name = "test"} };

    // tell the mock that when FindAll is called,
    // return the list of categories
    mockRepository.Setup(cr => cr.FindAll()).Returns(categories);

    // pass the mocked instance, not the mock itself, to the category
    // controller using the Object property
    var controller = new CategoryController(mockRepository.Object);

    // Act
    var result = (ViewResult) controller.List();

    // Assert
    var listCategories = Assert.IsAssignableFrom<IEnumerable<Category>>(result.ViewData.Model);
    Assert.Equal(1, listCategories.Count());            
}
Zu diesem Zeitpunkt ist die Anwendung gute suchen. Wir haben ein Repository, die während der Tests und eine Methode zum Imitieren dieses Repository, sodass wir das Controller-Verhalten isoliert testen können ersetzt werden kann. Für einige Anwendungen möglicherweise alle in Bezug auf den Domänencontrollern erforderlich ist, aber viele Mittel zu großen Anwendungen weiterhin eine weitere Abstraktionsebene Stelle.

Abgestufte Services
In einer komplexeren Anwendung können Sie eine besser abgestimmte Dienst Ansatz und eine Reihe von Aufgabe oder Ereignis Klassen, die die Aktionen darstellen, die im System ausgeführt werden können. Dies kann eine hat zwei Auswirkungen haben. Es kann zunächst die Größe und Komplexität Ihrer Dienstklassen reduzieren. Diese Klassen können zunehmen, sehr groß, wenn Sie nicht enthalten sind. Zweitens bietet es Sie mehr Kontrolle über Abhängigkeiten da jetzt nur die Abhängigkeiten der einzelnen Aktionen anstatt die Abhängigkeiten der Ihre gesamten Dienst kümmern müssen.
Implementieren von Service-Layer
Eine Dienstschicht befindet sich eine Anwendung Domänenmodell und bietet eine Reihe von Operationen, die gegen ausgeführt werden können. Dadurch können Sie einen Ort zum Zentralisieren der Logik, die in Ihrer Anwendung gehört, aber nicht unbedingt in das Domänenmodell gehören möglicherweise – Logik, die sonst wahrscheinlich in den Controller Methoden Verlust haben. Beispielsweise musste was passiert, wenn Sie eine Sicherheitsüberprüfung, um den Code im Abbildung 2? Sie möchten Ausführen des Vorgangs in der Aktion-Methode (Obwohl möglicherweise unter bestimmten Umständen) weil Wiederverwendung dieser Aktion an einem anderen Ort Sie schalten die Sicherheit zusammen mit dem erfordern würde. Oder, was geschieht, wenn Sie eine Buchung um einen Vorgang einfügen möchten? Sie möchten sicherlich nicht dieser Logik innerhalb der Controllerklasse.
Geben Sie stattdessen eine Schicht von Klassen, die eine Anwendung Dienste definieren und diese Klassen verwenden, um verschiedene Aktionen auszuführen. In einer einfachen Anwendung wie das Beispiel in diesem Artikel möglicherweise die Dienstschicht die Methoden für das Repository eng imitieren, können Sie im folgenden Code, aber wenn weitere Geschäftslogik die Anwendung dieser Methoden wahrscheinlich geschäftliche Aktionen anstatt durch grundlegende CRUD-Methoden darstellen würde:
public interface ICategoryService
{
    IEnumerable<Category> FindAll();
    Category FindById(int id);
    void Save(Category category);
}
Für die ersten beiden Methoden im Dienst Aufrufe werden im Repository delegiert und dann die Ergebnisse zurückgegeben werden, aber Sie vielleicht bemerkt, dass die Dienste eine Save-Methode stattdessen die Aktualisierungs- und Methoden für das Repository verfügen. Seit die Anwendung kann um zu ermitteln, ob ein Objekt wurde gespeichert, die Entscheidung, Update aufrufen oder Einfügen auf das Repository bis zum Dienst belassen werden.
Einfügen der Dienstschicht in der Mischung erheblich verringert die Kopplung zwischen den Klassen Controller und die Repository-Klassen während Ihre Domänencontroller heller beibehalten, da viel Logik in den Diensten übertragen werden können. Der Entwurf der Controller-Klassen muss für die Verwendung von Diensten anstelle des Repositorys, entsprechend geändert werden so anstelle von Repositorys in diesen Klassen einfügen, die Dienste einzuschleusen. Wiederum fügen Sie in den Diensten des Repositorys. Diese kompliziert, aber mit Dependency Injection sound könnte, Sie wird nicht selbst feststellen – alles für Sie verkabelte ruft.
Die gesamte Abhängigkeit Injection-Konfiguration (einschließlich der Produkt-Repository und der Dienst) sieht wie Abbildung 4.

Abbildung 4 Dependency Injection-Konfiguration

public class DefaultModule: NinjectModule
{
    public override void Load()
    {
        Bind<ICategoryService>().To<CategoryService>().InTransientScope();
        Bind<ICategoryRepository>().To<CategoryRepository>().InTransientScope();

        Bind<IProductService>().To<ProductService>().InTransientScope();
        Bind<IProductRepository>().To<ProductRepository>().InTransientScope();
    }
}
Nun, dass wir Repositorys und Dienste über Konstruktoren übergeben, müssen Sie die Domänencontroller verwenden des Dependency Injection-Frameworks zu erstellen. Das Dependency Injection-Framework übernimmt, Konstruktion und Injektion von Abhängigkeiten, jedoch müssen Sie die Controllerklasse vom Framework abgefragt. Dies ist viel einfacher als Sie erwarten können, da XML-Webdienste MVC-Teams Controller Factorys austauschbare vorgenommen. Einfach müssen Sie eine Controller Factory wie in Abbildung 5 implementieren, und Sie können leicht ersetzen die Instanziierung standardmäßig die Controller-Klassen.

Abbildung 5 Ninject Controller Factory

public class NinjectControllerFactory : DefaultControllerFactory
{
    private readonly IKernel kernel;

    public NinjectControllerFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    protected override IController GetControllerInstance(Type controllerType)
    {
        return (IController)kernel.Get(controllerType);
    }
}
Da die Controller Factory von der Standardimplementierung erbt, müssen Sie nur die GetControllerInstance-Methode überschreiben und fordern den Typ von der Ninject-Kernel, der die Klasse darstellt, die Objektinstanziierung in Ninject, gesteuert. Wenn der Kernel ein Domänencontroller empfängt, self-binds Ninject (versucht, zu erstellen) den Typ da es einen konkreten Typ handelt. Wenn der Kernel ein CategoryController erhält, haben der Konstruktor einen Parameter des Typs ICategoryService. Ninject prüft, wenn Sie eine Bindung für diesen Typ enthält, und wenn den Typ gefunden wird, führt die gleichen Aktion einen Konstruktor gesucht. Der Kernel Ninject instanziiert ein CategoryRepository und übergibt es an den Konstruktor für ControllerService. Übergibt Sie dann das Objekt ControllerService an den Konstruktor für die CategoryController. All, die dies innerhalb des Containers Dependency Injection geschieht einfach durch, die den Typ.
Die Controller Factory muss für die Arbeit registriert werden. Registrierung erfordert das Hinzufügen von nur einer einzigen Zeile in der global.asax-Datei:
public void Application_Start()
{
    // get the Ninject kernel for resolving dependencies
    kernel = CreateKernel();

    // set controller factory for instantiating controller and injecting dependencies
    ControllerBuilder.Current.SetControllerFactory(new NinjectController Factory(kernel));
}
Jetzt, wenn der Benutzer anfordert, eine URL und XML-Webdienste MVC versucht, einen Domänencontroller zu erstellen, endet Sie tatsächlich vom Dependency Injection-Container erstellt wird. Wenn der Domänencontroller getestet wird, können Sie den Dienst zum Testen des Controllers in Isolation, simulieren, wie in Abbildung 6 dargestellt.

Abbildung 6 Testen der Liste Aktion mit Mocked Service

[Fact]
public void ListActionReturnsListOfCategories()
{
    // Arrange
    var mockService = new Mock<ICategoryService>();
    var categories = new[] { new Category { Id = 1, Name = "test" } };
    mockService.Setup(cr => cr.FindAll()).Returns(categories);
    var controller = new CategoryController(mockService.Object);

    // Act
    var result = (ViewResult) controller.List();

    // Assert
    var listCategories = Assert.IsAssignableFrom<IEnumerable<Category>>(result.ViewData.Model);            
    Assert.Equal(1, listCategories.Count());
}

Logische Ansicht isolieren
Sie können an diesem Punkt vorstellen, dass Sie genügend Ebenen damit für vollständige Testbarkeit verfügen. Allerdings haben wir die Komplexität der Ansichten nicht noch untersucht. Genommen Sie an, ein Szenario, in denen Sie eine Produkt-Klasse mit einem Preis-Attribut haben. (Die Beispielanwendung zeigt dies.) Nachdem wir sagen, die Sie in diesem Preis für den Benutzer angezeigt werden soll als Währung formatiert. Einige Entwickler möglicherweise sagen Sie diese Anforderung in die Ansicht einfügen sollten, wie hier gezeigt:
<%= Html.Encode(String.Format("{0:c}", this.Model.Price)) %>
Ich gefällt nicht persönlich dieser Ansatz, da ich problemlos nicht Komponententests für diese Logik durchführen kann Wenn das Feld in der Ansicht formatiert. (Ich nicht über eine Benutzeroberfläche testen Framework mind, aber möglichst meine Tests wie möglich in Meine Komponententests ausgeführt werden soll.) Eine Lösung ist eine Eigenschaft der Produkt-Klasse auf den Preis für Anzeige formatieren hinzufügen, aber ich nicht gerne ausführen, da es bedeutet, dass die Aspekte der meine Ansicht Speicherverluste in der Domäne Schicht gestartet werden. Etwas so einfaches als Preis Format möglicherweise nicht wie ein großes Problem erscheinen, aber Probleme scheint immer zu erhöhen, wie Ihre Anwendung die Größe wächst und kleine Elemente Starten Probleme verursacht. Was geschieht beispielsweise, müssen Sie einen Preis anders auf zwei Seiten angezeigt? Starten Sie Ihre Domäne-Objekt Überladungen für jede andere Version hinzufügen?
Ein guter Ansatz ist die Verwendung der Präsentation Modell Muster. Präsentation Modelle zu und von Domänenobjekten zugeordnet werden und Ansicht-spezifische Logik und Werte, die leicht getestet werden können halten können. In der Beispielanwendung wurde ein Präsentationsmodell für jedes Formular erstellt. Da die meisten Formulare sehr ähnlich sind, erben Sie von freigegebenen Basisklassen. In einer komplexeren Anwendung würde jedoch diese wahrscheinlich sehr unterschiedlichen Codepages und Logik je nach auf dem Formular enthalten.
Wenn ein Präsentationsmodell eingesetzt wurden, würde die vorherige Anweisung stattdessen wie folgt aussehen:
<%= Html.Encode(this.Model.DisplayPrice) %>
Sie konnte diese-Eigenschaft problemlos in einem Komponententest wie in Abbildung 7 testen. Beachten Sie, dass die DisplayPrice auf die abstrakte Basisklasse so einen Stub platziert wurde wurde erstellt, die von dieser Basisklasse zur Erleichterung der Tests erbt. Beachten Sie, ein Stub nur ein Objekt, das für Testzwecke verwendet gefälschte Daten zurückgibt. Seit nicht abstrakte Klasse instanziiert werden kann, verwenden Sie den Stub, um die grundlegende Funktionalität zu testen.

Abbildung 7 Komponententests für eine Methode

[Fact]
public void PriceIsFormattedAsCurrency()
{
    // Arrange
    var product = new Product {Price = 9.22m};
    var presentationProduct = new 
        PresentationProductStub(product);

    // Act
    string cost = presentationProduct.DisplayPrice;

    // Assert
    Assert.Equal("$9.22", cost);
}
Die PresentationProductStub-Klasse wird von der PresentationProductBase-Klasse, die zum und vom Product-Klasse zugeordnet. Dadurch wird die Liste Aktion die ProductController-Klasse wie folgt aussehen:
public ActionResult List()
{
    var products = productService.FindAll();
    return View(new ProductList().MapList(products));
}
Die Produkte werden normalerweise vom Dienst abgerufen wird, jedoch bevor Sie die Ansicht gesendet werden, Sie werden übergeben an eine Methode für die ProductList-Klasse, die die Liste der Produkt-Klassen in eine Liste der ProductList Klassen konvertiert. Verwenden ein Modell für Ihre Ansichten hat mehrere unterschiedliche Vorteile. Zunächst wie Sie bereits gesehen haben, können Sie anzeigen-spezifische Verhalten zu Ihrer Klasse hinzufügen, die problemlos Komponententest unterzogen werden können. Dies ist nützlich für die Formatierung oder Transformieren von Daten für anzuzeigen. Zweitens können Sie verwenden die standardmäßige Modell Sammelmappen XML-Webdienste MVC integriert und platzieren Ihr Präsentationsmodell auf der Parameterliste einer POST-Methode, wie hier gezeigt:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ProductCreate productCreate)
{            
    productService.Save(productCreate.GetProduct());
    return RedirectToAction("List");            
}
Wenn Sie nicht bewusst der Standardbinder Modell XML-Webdienste MVC Funktionsweise sind, ordnet er einfach Felder im HTML-Code nach Namen auf die Eigenschaften der Klasse, die in der Liste der Parameter sind. Wenn ich die Produktklasse in der Parameterliste im vorstehenden Code eingefügt, konnte ein Benutzer böswillig ein Datenelement mit dem Namen "Id" POST.und überschreiben Sie die Taste auf das Modell. Dies ist offensichtlich keine gute Sache. Ein Benutzer konnte entsprechend die Namen eines beliebigen Feldes auf Meine Klasse übereinstimmen und die Werte überschreiben. Verwendung der Modelle in den Ansichten können Sie die Standard-Modell Sammelmappen verwenden (Wenn Sie auswählen) während Sie weiterhin steuern, welche Eigenschaften auf das Domänenobjekt zugeordnet sind.
Die Alternative ist Zuordnung Klassen zu definieren, die Sie zum Zuordnen von Entitäten eingehen über Aktion-Methode Parameter für Entitäten, die Sie in der Datenbank speichern möchten, verwenden können. Dadurch können Sie steuern welche Eigenschaften vor dem Speichern der Datenbank kopieren. Eine andere Option besteht darin, eine Reflektion-basierte Zuordnung Tool konnte Zuordnung der Felder die gewünschten ähnelt der erstellter Jimmy Bogard AutoMapper Tool zu erstellen.

Testen von Routen
Jetzt mit vielen der allgemeinen Architektur Tipps zum Testen aus dem Weg, wir konzentrieren besser abgestimmte testen Ansätze. Die erste Schritt implementieren eine ASP.NET MVC-Anwendung besteht darin, Tests zu überprüfen, das Verhalten der Routen innerhalb der Anwendung zu schreiben. Unbedingt sicherstellen, dass die base Route "~ /"wird an die entsprechenden Domänencontroller und Aktion weiterleiten und von anderen Domänencontrollern und Aktionen weiterleiten korrekt sowie. Es ist diese Tests an Stelle von Anfang verfügen, damit später, Ihre Anwendung mehrere Routen hinzugefügt werden, Sie garantiert, dass Routen, die bereits definiert wurden nicht fehlerhaft sind äußerst wichtig.
Zunächst definieren den Test für den standardmäßigen weiterleiten (siehe acht). In dieser Anwendung ist der Standard-Controller als "Kategorie" definiert.Domänencontroller und die Standard-Aktion in dieser Controller wird "Liste" festgelegt. Unsere Tests base Route überprüfen und bestätigen, dass die richtigen Werte in den Arbeitsplan-Daten enthalten sind.

Abbildung 8 testen Route Daten

[Fact]
public void DefaultUrlRoutesToCategoryControllerAndListAction()
{
    // Arrange
    var context = ContextHelper.GetMockHttpContext("~/");
    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);

    // Act
    RouteData routeData = routes.GetRouteData(context.Object);

    // Assert
    RouteTestingHelper.AssertRouteData(routeData, "Category", "List", "");
}
Dieser Test ist eine Hilfsfunktion verwenden, um die Werte für die Standardroute, die hier gezeigte assert:
public static void AssertRouteData(RouteData routeData, 
    string controller, string action, string id)
{
    Assert.NotNull(routeData);
    Assert.Equal(controller, routeData.Values["controller"]);
    Assert.Equal(action, routeData.Values["action"]);
    Assert.Equal(id, routeData.Values["id"]);
}
Diese dieselbe allgemeine Testmuster können Sie alle Routen, der auf das Domänencontroller, Aktion/ID-Muster (z. B. "~/Product/Edit/12"), an der HttpContext folgt übergeben und assert die Werte (siehe Abbildung 9).

Abbildung 9 prüfen Route-Daten mit Hilfe

[Fact]
public void ProductEditUrlRoutesToProductControllerAndListAction()
{
    // Arrange
    var context = ContextHelper.GetMockHttpContext("~/Product/Edit/12");
    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);

    // Act
    RouteData routeData = routes.GetRouteData(context.Object);

    // Assert
    RouteTestingHelper.AssertRouteData(routeData, "Product", "Edit", "12");
}
Mit dieser Methode die Tests für Routen sind etwas ausführlicher (für Übersichtlichkeit der Learning), und Sie sicherlich gekürzt. Ben Scheirman hat einige interessante Arbeit, die in diesem Bereich erledigt, und er hat eine gute Post in seinem Blog zum Vornehmen von sehr knappe Route Assertionen (" Fluent Route testen in XML-Webdienste MVC").

Abhängigkeiten von Aktionen als Parameter übergeben
Der zweite Tipp ist, alle Abhängigkeiten auf Domänencontroller Aktionen als Parameter an die Aktion-Methode übergeben. Ich verdeutlichen dies mit dem Datei-Download-Beispiel in der Beispielanwendung. Wie bereits früher eine HttpRequest in erwähnt die XML-Webdienste Runtime ist eine statische Klasse, die ist unglaublich schwierig zu ersetzen oder bei Komponententests simulieren. In MVC XML-Webdienste Wrapper-Klassen zur mocked werden diese Klassen ermöglichen, aber der Prozess der imitieren Sie oder stubbing diese kann weiterhin komplex sein.
Ein HttpRequestBase-Objekt simulieren, müssen Sie das HttpContextBase-Objekt, das es befindet sich in simulieren und füllen dann verschiedene Eigenschaften für jede. Für das Problem Speichern von Dateien müssen Sie die HttpServerUtilityBase zu simulieren, die sich auf HttpContextBase befindet. Anstatt mehrere mock-Objekte nachahmen möchten die unterschiedlichen Datenelemente der HttpContextBase erstellen, ist es wäre schön, stellen nur einen Parameter der Methode Aktion die HttpServerUtilityBase und dann in die einzelnen mocked Klasse während der Tests zu übergeben:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, ViewProduct viewProduct, 
    HttpServerUtilityBase server, HttpPostedFileBase imageFile)
{
Feststellen Sie, dass der dritte Parameter im vorhergehenden Code den Typ, den wir verwenden müssen und der Typ, den wir müssen simulieren. Mit der Methodensignatur aussehen müssen wir nur die HttpServerUtilityBase-Klasse zu simulieren. Wenn wir diese Klasse über this.HttpContext.Server zugegriffen wurde, wäre es zum Simulieren der HttpContextBase-Klasse gewesen. Das Problem ist, dass nur hinzufügen die HttpServerUtilityBase-Klasse um die Methodenparameter arbeiten; erleichtern wird nichtSie müssen eine Möglichkeit, XML-Webdienste MVC, wie diese Klasse instanziieren können. Dies ist das Framework Modell Sammelmappen kommen. Beachten Sie, dass HttpPostedFileBase bereits einen benutzerdefinierten Modell Binder standardmäßig zugewiesen hat.
Modell Sammelmappen sind Klassen, die die IModelBinder-Schnittstelle implementieren und bieten eine Möglichkeit XML-Webdienste MVC Typen auf Aktion Methoden zu instanziieren. In diesem Fall benötigen wir eine Sammelmappe Modell für die HttpServerUtilityBase. Wir können eine Klasse erstellen, die wie folgt aussieht:
public class HttpServerModelBinder: IModelBinder
{
    public object BindModel(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        return controllerContext.HttpContext.Server;
    }
}
Der Binder Modell hat Zugriff auf sowohl der Controller-Kontext und einen Bindungskontext. Dieses Modell Binder greift auf den Controller Kontext einfach und gibt die HttpServerUtilityBase-Klasse in der HttpContext-Klasse zurück. Alle bleibt besteht darin, der XML-Webdienste MVC-Runtime mitzuteilen dieser Sammelmappe Modell verwenden Wenn Sie einen Parameter des Typs HttpServerUtilityBase findet. Beachten Sie, dass dieser Code so wie in der Datei Global.asax gesetzt wird:
public void Application_Start()
{
    // assign model binder for passing in the 
    // HttpServerUtilityBase to controller actions
    ModelBinders.Binders.Add(typeof(HttpServerUtilityBase), new HttpServerModelBinder());
}
Ein HttpServerUtilityBase als Parameter an eine Aktion-Methode übergeben wird, ruft die XML-Webdienste MVC-Laufzeit jetzt die BindModel-Methode auf HttpServerModelBinder um eine Instanz der Klasse abzurufen. Es ist sehr einfach Modell Sammelmappen erstellen, und Sie Test Controller-Aktionen viel leichter machen.

Testen mit Aktion Ergebnisse
Eine frühzeitige Beschwerden über XML-Webdienste MVC betrifft die Schwierigkeit der Tests Operationen wie z. B. die Ansicht von Aktion-Methode wiedergegeben wurde. Mussten Sie die Controller-Kontext, eine Ansicht Modul usw. zu simulieren. Es war wirklich mühsam. In der Vorschau 3 hinzugefügt XML-Webdienste MVC-Team das Konzept der Aktion-Ergebnisse, die Klassen, die von der ActionResult-Klasse abgeleitet und eine Aufgabe, die die Aktion führen darstellen. Jetzt in XML-Webdienste MVC jede Aktion-Methode gibt den Typ ActionResult und ein Entwickler kann aus einer Reihe von integrierten Aktion-Ergebnistypen wählen oder erstellen seines eigenen. Dies hilft beim Testen, da Sie können eine Aktion-Methode aufrufen und überprüfen das Ergebnis finden Sie unter Was ist aufgetreten. Werfen Sie das Beispiel für eine ViewResult, die wir wie hier gezeigt durch Aufrufen der Ansicht-Methode der Basisklasse von Domänencontroller erstellen können:
public ActionResult List()
{
    IEnumerable<Category> categories = categoryService.FindAll();
    return View(ViewCategory.MapList(categories));
}
In dieser Aktion-Methode eine Liste von Kategorien wird als Modell zur Ansicht übergeben, aber keine Ansichtsname angegeben wird. Dies bedeutet, dass diese Aktion die Standardansicht mit dem Namen Liste dargestellt wird. Wenn Sie diese Aktion immer eine leere Ansichtsnamen zurück sicherstellen möchten, können Sie einen Test wie Abbildung 10 schreiben.

Abbildung 10 testen Aktion Ergebnisse

[Fact]
public void ListActionReturnsViewResultWithDefaultName()
{
    // Arrange
    var mockService = new Mock<ICategoryService>();
    var controller = new CategoryController(mockService.Object);

    // Act
    var result = controller.List();

    // Assert
    var viewResult = Assert.IsType<ViewResult>(result);            
    Assert.Empty(viewResult.ViewName);
}
Dieser Test mocks CategoryService um die Controllerklasse erstellen. Der Test ruft dann die Liste Aktion zum Abrufen des ActionResult-Objekts. Der Test Assertionen schließlich, dass das Ergebnis vom Typ ViewResult ist und der Name der Ansicht leer ist. Es wäre sehr einfach, Testen den Ansichtsnamen gegen einen bekannten Zeichenfolgenwert oder in Fällen, in dem Sie der Rückgabetyp ist etwas etwas komplexer (JSON Format, z. B.), so überprüfen Sie die Data-Eigenschaft auf das Ergebnis, um sicherzustellen, dass er die erwartete Informationen, enthält wie durch einen Aufruf der Aktion-Methode zurückgegeben.

Empfohlene Literatur
  • Agile Principles, Patterns and Practices in c# von Robert C. Martin und Micah Müller (Prentice Hall, 2006)
  • Muster für Enterprise Application Architecture von Martin Fowler (Addison-Wesley Professional, 2002)
  • Microsoft .NET: Entwickeln von Anwendungen für die Enterprise- von Dino Esposito und Andrea Saltarello (Microsoft Press, 2008)
Zusammenfassung
XML-Webdienste MVC-Team hat sehr viel Aufwand investiert, erstellen Sie eine flexible Architektur, die für einfache Tests ermöglicht. Entwickler können jetzt weitere Ihre Systeme einfach aufgrund von Features wie austauschbare Controller Factorys, Aktion Ergebnistypen und Wrapper Testen der XML-Webdienste Kontexttypen. All dies bietet ASP.NET MVC Entwicklern ein guter Ausgangspunkt, aber die Onus ist weiterhin auf Entwickler entwerfen und Erstellen von Anwendungen, die lose gekoppelten und getestet werden. Ich hoffe, hilft in diesem Artikel als Fortschritt Sie unten in diesem Pfad. Wenn Sie interessiert sind, habe ich diese in der Randleiste Empfohlene Literatur aufgeführt.

Justin Etheredge ist eine Microsoft C#-MVP, Autor CodeThinked.com und Gründer von Richmond Software handwerkliches können Group. Er ist leitender Berater bei Dominion Digital in Richmond, Virginia, stellt er Anleitung im Entwerfen und Erstellen von Systemen für alle Formen und Größen bereit.

MSDN Magazine Blog

MSDN Magazine Right Rail

14 Top Features of Visual Basic 14: The Q&A
Leading off the feature in the January issue of MSDN Magazine is Lucian Wischik’s fantastic look at Visual Basic .NET 14. As Wischik writes, the newes... More...
Mittwoch, Jan 7
Big Start to the New Year at MSDN Magazine
Folks, things are hopping over here at MSDN Magazine. We are kicking off the new year with a pair of issues: Our regularly scheduled January issue and... More...
Freitag, Jan 2

More MSDN Magazine Blog entries >


Receive the MSDN Flash e-mail newsletter every other week, with news and information personalized to your interests and areas of focus.