Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
von Microsoft
In dieser sechsten Iteration fügen wir unserer Anwendung neue Funktionen hinzu, indem wir zuerst Komponententests schreiben und Code für die Komponententests schreiben. In dieser Iteration fügen wir Kontaktgruppen hinzu.
Erstellen einer Kontaktverwaltung ASP.NET MVC-Anwendung (C#)
In dieser Reihe von Tutorials erstellen wir eine gesamte Kontaktverwaltungsanwendung von Anfang bis Ende. Mit der Contact Manager-Anwendung können Sie Kontaktinformationen – Namen, Telefonnummern und E-Mail-Adressen – für eine Liste von Personen speichern.
Wir erstellen die Anwendung über mehrere Iterationen. Mit jeder Iteration verbessern wir die Anwendung schrittweise. Das Ziel dieses Ansatzes für mehrere Iterationen besteht darin, ihnen zu ermöglichen, den Grund für jede Änderung zu verstehen.
Iteration #1: Erstellen Sie die Anwendung. In der ersten Iteration erstellen wir den Contact Manager auf einfachste Weise. Wir fügen Unterstützung für grundlegende Datenbankvorgänge hinzu: Erstellen, Lesen, Aktualisieren und Löschen (CRUD).
Iteration #2: Lassen Sie die Anwendung schön aussehen. In dieser Iteration verbessern wir das Erscheinungsbild der Anwendung, indem wir die Standardansicht ASP.NET MVC master Seite und cascading Stylesheet ändern.
Iteration #3: Hinzufügen der Formularüberprüfung. In der dritten Iteration fügen wir die grundlegende Formularüberprüfung hinzu. Wir verhindern, dass Personen ein Formular einreichen, ohne die erforderlichen Formularfelder ausfüllen zu müssen. Außerdem überprüfen wir E-Mail-Adressen und Telefonnummern.
Iteration #4: Machen Sie die Anwendung lose gekoppelt. In dieser vierten Iteration nutzen wir mehrere Softwareentwurfsmuster, um die Verwaltung und Änderung der Contact Manager-Anwendung zu vereinfachen. Beispielsweise umgestalten wir unsere Anwendung so, dass sie das Repositorymuster und das Abhängigkeitsinjektionsmuster verwendet.
Iteration #5: Erstellen von Komponententests. In der fünften Iteration erleichtern wir die Wartung und Änderung unserer Anwendung durch Hinzufügen von Komponententests. Wir simulieren unsere Datenmodellklassen und erstellen Komponententests für unsere Controller und die Validierungslogik.
Iteration #6: Verwenden Sie die testgesteuerte Entwicklung. In dieser sechsten Iteration fügen wir unserer Anwendung neue Funktionen hinzu, indem wir zuerst Komponententests schreiben und Code für die Komponententests schreiben. In dieser Iteration fügen wir Kontaktgruppen hinzu.
Iteration #7: Hinzufügen von Ajax-Funktionen. In der siebten Iteration verbessern wir die Reaktionsfähigkeit und Leistung unserer Anwendung, indem wir Unterstützung für Ajax hinzufügen.
Diese Iteration
In der vorherigen Iteration der Contact Manager-Anwendung haben wir Komponententests erstellt, um ein Sicherheitsnetz für unseren Code bereitzustellen. Die Motivation für die Erstellung der Komponententests bestand darin, unseren Code gegenüber Änderungen widerstandsfähiger zu machen. Wenn Komponententests eingerichtet sind, können wir gerne änderungen am Code vornehmen und sofort wissen, ob wir vorhandene Funktionen unterbrochen haben.
In dieser Iteration verwenden wir Komponententests für einen ganz anderen Zweck. In dieser Iteration verwenden wir Komponententests als Teil einer Anwendungsentwurfsphilosophie, die als testgesteuerte Entwicklung bezeichnet wird. Wenn Sie die testgesteuerte Entwicklung üben, schreiben Sie zuerst Tests und dann Code für die Tests.
Genauer gesagt gibt es beim Ausführen der testgesteuerten Entwicklung drei Schritte, die Sie beim Erstellen von Code ausführen (Rot/Grün/Refactor):
- Schreiben eines Komponententests, bei dem ein Fehler auftritt (Rot)
- Schreiben von Code, der den Komponententest besteht (Grün)
- Umgestalten des Codes (Refactor)
Zunächst schreiben Sie den Komponententest. Der Komponententest sollte Ihre Absicht zum Ausdruck bringen, wie Sich Ihr Code voraussichtlich verhält. Wenn Sie den Komponententest zum ersten Mal erstellen, sollte der Komponententest fehlschlagen. Der Test sollte fehlschlagen, da Sie noch keinen Anwendungscode geschrieben haben, der den Test erfüllt.
Als Nächstes schreiben Sie gerade genug Code, damit der Komponententest erfolgreich ist. Das Ziel ist es, den Code auf die faulste, schlampigste und schnellstmögliche Weise zu schreiben. Sie sollten keine Zeit damit verschwenden, über die Architektur Ihrer Anwendung nachzudenken. Stattdessen sollten Sie sich darauf konzentrieren, die minimale Menge an Code zu schreiben, die erforderlich ist, um die im Komponententest zum Ausdruck gebrachte Absicht zu erfüllen.
Nachdem Sie genügend Code geschrieben haben, können Sie schließlich einen Schritt zurücktreten und die gesamte Architektur Ihrer Anwendung berücksichtigen. In diesem Schritt schreiben Sie Ihren Code neu( umgestalten), indem Sie Softwareentwurfsmuster nutzen , z. B. das Repositorymuster, damit Ihr Code verwaltbarer ist. Sie können Ihren Code in diesem Schritt furchtlos umschreiben, da Ihr Code durch Komponententests abgedeckt wird.
Es gibt viele Vorteile, die sich aus der praxisorientierten Entwicklung ergeben. Die testgesteuerte Entwicklung zwingt Sie zunächst dazu, sich auf Code zu konzentrieren, der tatsächlich geschrieben werden muss. Da Sie sich ständig darauf konzentrieren, nur genügend Code zu schreiben, um einen bestimmten Test zu bestehen, sind Sie daran gehindert, in die Unkraut zu wandern und riesige Mengen an Code zu schreiben, die Sie nie verwenden werden.
Zweitens erzwingen Sie durch eine Entwurfsmethodik zum Testen zuerst das Schreiben von Code aus der Perspektive, wie Ihr Code verwendet wird. Mit anderen Worten, wenn Sie testgesteuerte Entwicklung üben, schreiben Sie Ihre Tests ständig aus der Perspektive des Benutzers. Daher kann die testgesteuerte Entwicklung zu saubereren und verständlicheren APIs führen.
Die testgesteuerte Entwicklung zwingt Sie schließlich dazu, Komponententests im Rahmen des normalen Prozesses zum Schreiben einer Anwendung zu schreiben. Wenn sich ein Projekttermin nähert, ist das Testen in der Regel das erste, was aus dem Fenster geht. Bei der testgesteuerten Entwicklung hingegen sind Sie beim Schreiben von Komponententests eher tugendhaft, da die testgesteuerte Entwicklung Komponententests für den Erstellungsprozess einer Anwendung von zentraler Bedeutung macht.
Hinweis
Um mehr über die testgesteuerte Entwicklung zu erfahren, empfehle ich Ihnen, das Michael Feathers-Buch Arbeiten effektiv mit Legacy Code zu lesen.
In dieser Iteration fügen wir unserer Contact Manager-Anwendung ein neues Feature hinzu. Wir fügen Unterstützung für Kontaktgruppen hinzu. Sie können Kontaktgruppen verwenden, um Ihre Kontakte in Kategorien wie Geschäfts- und Freundesgruppen zu organisieren.
Wir fügen diese neue Funktionalität unserer Anwendung hinzu, indem wir einem testgesteuerten Entwicklungsprozess folgen. Wir schreiben zuerst unsere Komponententests, und wir schreiben den gesamten Code für diese Tests.
Was getestet wird
Wie in der vorherigen Iteration erläutert, schreiben Sie in der Regel keine Komponententests für Datenzugriffslogik oder Sichtlogik. Sie schreiben keine Komponententests für die Datenzugriffslogik, da der Zugriff auf eine Datenbank relativ langsam ist. Sie schreiben keine Komponententests für die Ansichtslogik, da für den Zugriff auf eine Ansicht das Hochdrehen eines Webservers erforderlich ist, was ein relativ langsamer Vorgang ist. Sie sollten keinen Komponententest schreiben, es sei denn, der Test kann immer wieder sehr schnell ausgeführt werden.
Da die testgesteuerte Entwicklung durch Komponententests gesteuert wird, konzentrieren wir uns zunächst auf das Schreiben von Controllern und Geschäftslogik. Es wird vermieden, die Datenbank oder Ansichten zu berühren. Wir werden die Datenbank erst am Ende dieses Tutorials ändern oder unsere Ansichten erstellen. Wir beginnen mit dem, was getestet werden kann.
Erstellen von User Storys
Bei der testgesteuerten Entwicklung beginnen Sie immer mit dem Schreiben eines Tests. Dies wirft sofort die Frage auf: Wie entscheiden Sie, welcher Test zuerst geschrieben werden soll? Um diese Frage zu beantworten, sollten Sie eine Reihe von User Storys schreiben.
Eine User Story ist eine sehr kurze (in der Regel ein Satz) Beschreibung einer Softwareanforderung. Es sollte eine nicht technische Beschreibung einer Anforderung sein, die aus der Sicht des Benutzers geschrieben wird.
Hier finden Sie die Gruppe von User Storys, in denen die Features beschrieben werden, die für die neue Kontaktgruppenfunktionalität erforderlich sind:
- Benutzer können eine Liste von Kontaktgruppen anzeigen.
- Der Benutzer kann eine neue Kontaktgruppe erstellen.
- Der Benutzer kann eine vorhandene Kontaktgruppe löschen.
- Der Benutzer kann beim Erstellen eines neuen Kontakts eine Kontaktgruppe auswählen.
- Der Benutzer kann eine Kontaktgruppe auswählen, wenn er einen vorhandenen Kontakt bearbeitet.
- In der Indexansicht wird eine Liste von Kontaktgruppen angezeigt.
- Wenn ein Benutzer auf eine Kontaktgruppe klickt, wird eine Liste mit übereinstimmenden Kontakten angezeigt.
Beachten Sie, dass diese Liste der Benutzergeschichten für einen Kunden vollständig verständlich ist. Es gibt keine Erwähnung technischer Implementierungsdetails.
Während des Erstellens Ihrer Anwendung kann die Gruppe der Benutzergeschichten verfeinert werden. Sie können einen Benutzerabschnitt in mehrere Storys (Anforderungen) aufteilen. Sie können z. B. entscheiden, dass das Erstellen einer neuen Kontaktgruppe eine Überprüfung erfordern sollte. Beim Übermitteln einer Kontaktgruppe ohne Namen sollte ein Validierungsfehler zurückgegeben werden.
Nachdem Sie eine Liste mit User Storys erstellt haben, können Sie Ihren ersten Komponententest schreiben. Zunächst erstellen wir einen Komponententest zum Anzeigen der Liste der Kontaktgruppen.
Auflisten von Kontaktgruppen
Unsere erste User Story besteht darin, dass ein Benutzer in der Lage sein sollte, eine Liste von Kontaktgruppen anzuzeigen. Wir müssen diese Geschichte mit einem Test ausdrücken.
Erstellen Sie einen neuen Komponententest, indem Sie im Projekt ContactManager.Tests mit der rechten Maustaste auf den Ordner Controller klicken , Hinzufügen, Neuer Test und dann die Vorlage Komponententest auswählen (siehe Abbildung 1). Nennen Sie den neuen Komponententest GroupControllerTest.cs, und klicken Sie auf die Schaltfläche OK .
Abbildung 01: Hinzufügen des GroupControllerTest-Komponententests(Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Unser erster Komponententest ist in Listing 1 enthalten. Dieser Test überprüft, ob die Index()-Methode des Group-Controllers eine Gruppe von Gruppen zurückgibt. Der Test überprüft, ob eine Sammlung von Gruppen in Ansichtsdaten zurückgegeben wird.
Auflistung 1: Controllers\GroupControllerTest.cs
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web.Mvc;
using ContactManager.Models;
namespace ContactManager.Tests.Controllers
{
[TestClass]
public class GroupControllerTest
{
[TestMethod]
public void Index()
{
// Arrange
var controller = new GroupController();
// Act
var result = (ViewResult)controller.Index();
// Assert
Assert.IsInstanceOfType(result.ViewData.Model, typeof(IEnumerable));
}
}
}
Wenn Sie den Code zum ersten Mal in Listing 1 in Visual Studio eingeben, erhalten Sie viele rote Wellenlinien. Die GroupController- oder Group-Klasse wurde nicht erstellt.
An diesem Punkt können wir nicht einmal unsere Anwendung erstellen, sodass wir unseren ersten Komponententest nicht ausführen können. Das ist gut. Dies gilt als fehlerhafter Test. Daher haben wir jetzt die Berechtigung, mit dem Schreiben von Anwendungscode zu beginnen. Wir müssen genügend Code schreiben, um unseren Test auszuführen.
Die Group controller-Klasse in Listing 2 enthält das mindeste Maß an Code, der zum Bestehen des Komponententests erforderlich ist. Die Index()-Aktion gibt eine statisch codierte Liste von Gruppen zurück (die Group-Klasse ist in Listing 3 definiert).
Auflistung 2: Controllers\GroupController.cs
using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Models;
namespace ContactManager.Controllers
{
public class GroupController : Controller
{
public ActionResult Index()
{
var groups = new List();
return View(groups);
}
}
}
Auflistung 3: Models\Group.cs
namespace ContactManager.Models
{
public class Group
{
}
}
Nachdem wir dem Projekt die Klassen GroupController und Group hinzugefügt haben, wird der erste Komponententest erfolgreich abgeschlossen (siehe Abbildung 2). Wir haben die minimale Arbeit geleistet, die erforderlich ist, um den Test zu bestehen. Es ist Zeit zu feiern.
Abbildung 02: Erfolg! (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Erstellen von Kontaktgruppen
Jetzt können wir mit der zweiten User Story fortfahren. Wir müssen in der Lage sein, neue Kontaktgruppen zu erstellen. Wir müssen diese Absicht mit einem Test ausdrücken.
Der Test in Listing 4 überprüft, ob beim Aufrufen der Create()-Methode mit einer neuen Group die Group der Liste der Von der Index()-Methode zurückgegebenen Gruppen hinzugefügt wird. Anders ausgedrückt: Wenn ich eine neue Gruppe erstelle, sollte ich die neue Gruppe aus der Liste der Gruppen abrufen können, die von der Index()-Methode zurückgegeben werden.
Auflistung 4: Controllers\GroupControllerTest.cs
[TestMethod]
public void Create()
{
// Arrange
var controller = new GroupController();
// Act
var groupToCreate = new Group();
controller.Create(groupToCreate);
// Assert
var result = (ViewResult)controller.Index();
var groups = (IEnumerable<Group>)result.ViewData.Model;
CollectionAssert.Contains(groups.ToList(), groupToCreate);
}
Der Test in Listing 4 ruft die Create()-Methode des Gruppencontrollers mit einer neuen Kontaktgruppe auf. Als Nächstes überprüft der Test, ob der Aufruf der Index()-Methode des Gruppencontrollers die neue Group in Ansichtsdaten zurückgibt.
Der geänderte Gruppencontroller in Listing 5 enthält das Minimum an Änderungen, die zum Bestehen des neuen Tests erforderlich sind.
Auflistung 5 : Controllers\GroupController.cs
using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Models;
using System.Collections;
namespace ContactManager.Controllers
{
public class GroupController : Controller
{
private IList<Group> _groups = new List<Group>();
public ActionResult Index()
{
return View(_groups);
}
public ActionResult Create(Group groupToCreate)
{
_groups.Add(groupToCreate);
return RedirectToAction("Index");
}
}
}
Der Gruppencontroller in Listing 5 verfügt über eine neue Create()-Aktion. Diese Aktion fügt einer Sammlung von Gruppen eine Gruppe hinzu. Beachten Sie, dass die Index()-Aktion geändert wurde, um den Inhalt der Auflistung von Gruppen zurückzugeben.
Wieder einmal haben wir die minimale Menge an Arbeit ausgeführt, die erforderlich ist, um den Komponententest zu bestehen. Nachdem wir diese Änderungen am Gruppencontroller vorgenommen haben, werden alle Komponententests erfolgreich ausgeführt.
Hinzufügen der Validierung
Diese Anforderung wurde in der User Story nicht explizit angegeben. Es ist jedoch sinnvoll, vorzuschreiben, dass eine Gruppe über einen Namen verfügt. Andernfalls wäre das Organisieren von Kontakten in Gruppen nicht sehr nützlich.
Listing 6 enthält einen neuen Test, der diese Absicht zum Ausdruck bringt. Dieser Test überprüft, ob der Versuch, eine Gruppe zu erstellen, ohne einen Namen anzugeben, zu einer Validierungsfehlermeldung im Modellzustand führt.
Auflistung 6: Controllers\GroupControllerTest.cs
[TestMethod]
public void CreateRequiredName()
{
// Arrange
var controller = new GroupController();
// Act
var groupToCreate = new Group();
groupToCreate.Name = String.Empty;
var result = (ViewResult)controller.Create(groupToCreate);
// Assert
var error = result.ViewData.ModelState["Name"].Errors[0];
Assert.AreEqual("Name is required.", error.ErrorMessage);
}
Um diesen Test zu erfüllen, müssen wir unserer Group-Klasse eine Name-Eigenschaft hinzufügen (siehe Listing 7). Darüber hinaus müssen wir der Create()-Aktion des Gruppencontrollers ein kleines Bisschen Validierungslogik hinzufügen (siehe Auflistung 8).
Auflistung 7: Models\Group.cs
namespace ContactManager.Models
{
public class Group
{
public string Name { get; set; }
}
}
Auflistung 8 : Controllers\GroupController.cs
public ActionResult Create(Group groupToCreate)
{
// Validation logic
if (groupToCreate.Name.Trim().Length == 0)
{
ModelState.AddModelError("Name", "Name is required.");
return View("Create");
}
// Database logic
_groups.Add(groupToCreate);
return RedirectToAction("Index");
}
Beachten Sie, dass die Gruppierungscontrolleraktion Create() jetzt sowohl validierungs- als auch Datenbanklogik enthält. Derzeit besteht die vom Gruppencontroller verwendete Datenbank aus nichts weiter als einer In-Memory-Sammlung.
Zeit für die Umgestaltung
Der dritte Schritt in Rot/Grün/Refactor ist der Refactor-Teil. An diesem Punkt müssen wir uns von unserem Code abwenden und überlegen, wie wir unsere Anwendung umgestalten können, um den Entwurf zu verbessern. Die Refactor-Phase ist die Phase, in der wir uns intensiv gedanken über die beste Methode zur Implementierung von Softwareentwurfsprinzipien und -mustern machen.
Wir können unseren Code in jeder Weise ändern, die wir zur Verbesserung des Entwurfs des Codes auswählen. Wir verfügen über ein Sicherheitsnetz von Komponententests, die verhindern, dass wir vorhandene Funktionen unterbrechen.
Im Moment ist unser Group Controller unter der Perspektive eines guten Softwaredesigns ein Chaos. Der Gruppencontroller enthält eine Vielzahl von Validierungs- und Datenzugriffscode. Um zu verhindern, dass das Prinzip der einheitlichen Verantwortung verletzt wird, müssen wir diese Bedenken in verschiedene Klassen unterteilen.
Unsere refactored Group Controller-Klasse ist in Listing 9 enthalten. Der Controller wurde so geändert, dass er die ContactManager-Dienstebene verwendet. Dies ist die gleiche Dienstebene, die wir mit dem Kontaktcontroller verwenden.
Listing 10 enthält die neuen Methoden, die der ContactManager-Dienstebene hinzugefügt wurden, um das Überprüfen, Auflisten und Erstellen von Gruppen zu unterstützen. Die IContactManagerService-Schnittstelle wurde aktualisiert, um die neuen Methoden einzuschließen.
Listing 11 enthält eine neue FakeContactManagerRepository-Klasse, die die IContactManagerRepository-Schnittstelle implementiert. Im Gegensatz zur EntityContactManagerRepository-Klasse, die auch die IContactManagerRepository-Schnittstelle implementiert, kommuniziert unsere neue FakeContactManagerRepository-Klasse nicht mit der Datenbank. Die FakeContactManagerRepository-Klasse verwendet eine In-Memory-Sammlung als Proxy für die Datenbank. Wir verwenden diese Klasse in unseren Komponententests als gefälschte Repositoryebene.
Auflistung 9: Controllers\GroupController.cs
using System.Web.Mvc;
using ContactManager.Models;
namespace ContactManager.Controllers
{
public class GroupController : Controller
{
private IContactManagerService _service;
public GroupController()
{
_service = new ContactManagerService(new ModelStateWrapper(this.ModelState));
}
public GroupController(IContactManagerService service)
{
_service = service;
}
public ActionResult Index()
{
return View(_service.ListGroups());
}
public ActionResult Create(Group groupToCreate)
{
if (_service.CreateGroup(groupToCreate))
return RedirectToAction("Index");
return View("Create");
}
}
}
Auflistung 10 : Controllers\ContactManagerService.cs
public bool ValidateGroup(Group groupToValidate)
{
if (groupToValidate.Name.Trim().Length == 0)
_validationDictionary.AddError("Name", "Name is required.");
return _validationDictionary.IsValid;
}
public bool CreateGroup(Group groupToCreate)
{
// Validation logic
if (!ValidateGroup(groupToCreate))
return false;
// Database logic
try
{
_repository.CreateGroup(groupToCreate);
}
catch
{
return false;
}
return true;
}
public IEnumerable<Group> ListGroups()
{
return _repository.ListGroups();
}
Auflistung 11 : Controllers\FakeContactManagerRepository.cs
using System;
using System.Collections.Generic;
using ContactManager.Models;
namespace ContactManager.Tests.Models
{
public class FakeContactManagerRepository : IContactManagerRepository
{
private IList<Group> _groups = new List<Group>();
#region IContactManagerRepository Members
// Group methods
public Group CreateGroup(Group groupToCreate)
{
_groups.Add(groupToCreate);
return groupToCreate;
}
public IEnumerable<Group> ListGroups()
{
return _groups;
}
// Contact methods
public Contact CreateContact(Contact contactToCreate)
{
throw new NotImplementedException();
}
public void DeleteContact(Contact contactToDelete)
{
throw new NotImplementedException();
}
public Contact EditContact(Contact contactToEdit)
{
throw new NotImplementedException();
}
public Contact GetContact(int id)
{
throw new NotImplementedException();
}
public IEnumerable<Contact> ListContacts()
{
throw new NotImplementedException();
}
#endregion
}
}
Zum Ändern der IContactManagerRepository-Schnittstelle müssen die Methoden CreateGroup() und ListGroups() in der EntityContactManagerRepository-Klasse implementiert werden. Die faulste und schnellste Möglichkeit, dies zu tun, besteht darin, Stubmethoden hinzuzufügen, die wie folgt aussehen:
public Group CreateGroup(Group groupToCreate)
{
throw new NotImplementedException();
}
public IEnumerable<Group> ListGroups()
{
throw new NotImplementedException();
}
Schließlich erfordern diese Änderungen am Entwurf unserer Anwendung einige Änderungen an unseren Komponententests. Wir müssen jetzt den FakeContactManagerRepository verwenden, wenn wir die Komponententests durchführen. Die aktualisierte GroupControllerTest-Klasse ist in Listing 12 enthalten.
Auflistung 12: Controllers\GroupControllerTest.cs
using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Controllers;
using ContactManager.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections;
using System.Linq;
using System;
using ContactManager.Tests.Models;
namespace ContactManager.Tests.Controllers
{
[TestClass]
public class GroupControllerTest
{
private IContactManagerRepository _repository;
private ModelStateDictionary _modelState;
private IContactManagerService _service;
[TestInitialize]
public void Initialize()
{
_repository = new FakeContactManagerRepository();
_modelState = new ModelStateDictionary();
_service = new ContactManagerService(new ModelStateWrapper(_modelState), _repository);
}
[TestMethod]
public void Index()
{
// Arrange
var controller = new GroupController(_service);
// Act
var result = (ViewResult)controller.Index();
// Assert
Assert.IsInstanceOfType(result.ViewData.Model, typeof(IEnumerable));
}
[TestMethod]
public void Create()
{
// Arrange
var controller = new GroupController(_service);
// Act
var groupToCreate = new Group();
groupToCreate.Name = "Business";
controller.Create(groupToCreate);
// Assert
var result = (ViewResult)controller.Index();
var groups = (IEnumerable)result.ViewData.Model;
CollectionAssert.Contains(groups.ToList(), groupToCreate);
}
[TestMethod]
public void CreateRequiredName()
{
// Arrange
var controller = new GroupController(_service);
// Act
var groupToCreate = new Group();
groupToCreate.Name = String.Empty;
var result = (ViewResult)controller.Create(groupToCreate);
// Assert
var error = _modelState["Name"].Errors[0];
Assert.AreEqual("Name is required.", error.ErrorMessage);
}
}
}
Nachdem wir alle diese Änderungen vorgenommen haben, werden alle unsere Komponententests erneut erfolgreich ausgeführt. Wir haben den gesamten Rot/Grün/Refactor-Zyklus abgeschlossen. Wir haben die ersten beiden User Storys implementiert. Wir verfügen jetzt über unterstützende Komponententests für die Anforderungen, die in den User Storys ausgedrückt werden. Die Implementierung der restlichen User Storys umfasst die Wiederholung desselben Zyklus von Rot/Grün/Umgestalten.
Ändern der Datenbank
Obwohl wir alle Anforderungen unserer Komponententests erfüllt haben, ist unsere Arbeit leider nicht abgeschlossen. Die Datenbank muss noch geändert werden.
Wir müssen eine neue Gruppendatenbanktabelle erstellen. Führen Sie die folgenden Schritte aus:
- Klicken Sie im Fenster Server Explorer mit der rechten Maustaste auf den Ordner Tabellen, und wählen Sie die Menüoption Neue Tabelle hinzufügen aus.
- Geben Sie die beiden unten beschriebenen Spalten im Designer Tabelle ein.
- Markieren Sie die Id-Spalte als Primärschlüssel und identitätsspalten.
- Speichern Sie die neue Tabelle mit dem Namen Gruppen, indem Sie auf das Symbol der Diskette klicken.
Spaltenname | Datentyp | NULL-Werte zulassen |
---|---|---|
Id | INT | False |
Name | nvarchar(50) | False |
Als Nächstes müssen wir alle Daten aus der Tabelle Kontakte löschen (andernfalls können wir keine Beziehung zwischen den Tabellen Kontakte und Gruppen erstellen). Führen Sie die folgenden Schritte aus:
- Klicken Sie mit der rechten Maustaste auf die Tabelle Kontakte, und wählen Sie die Menüoption Tabellendaten anzeigen aus.
- Löschen Sie alle Zeilen.
Als Nächstes muss eine Beziehung zwischen der Gruppendatenbanktabelle und der vorhandenen Contacts-Datenbanktabelle definiert werden. Führen Sie die folgenden Schritte aus:
- Doppelklicken Sie im Fenster Server Explorer auf die Tabelle Kontakte, um die tabelle Designer zu öffnen.
- Fügen Sie der Tabelle Kontakte eine neue ganzzahlige Spalte mit dem Namen GroupId hinzu.
- Klicken Sie auf die Schaltfläche Beziehung, um das Dialogfeld Fremdschlüsselbeziehungen zu öffnen (siehe Abbildung 3).
- Klicken Sie auf die Schaltfläche Hinzufügen.
- Klicken Sie auf die Schaltfläche mit den Auslassungspunkten, die neben der Schaltfläche Tabelle und Spaltenspezifikation angezeigt wird.
- Wählen Sie im Dialogfeld Tabellen und Spalten die Option Gruppen als Primärschlüsseltabelle und ID als Primärschlüsselspalte aus. Wählen Sie Kontakte als Fremdschlüsseltabelle und GroupId als Fremdschlüsselspalte aus (siehe Abbildung 4). Klicken Sie auf die Schaltfläche "OK".
- Wählen Sie unter INSERT- und UPDATE-Spezifikation den Wert Cascade für Regel löschen aus.
- Klicken Sie auf die Schaltfläche Schließen, um das Dialogfeld Fremdschlüsselbeziehungen zu schließen.
- Klicken Sie auf die Schaltfläche Speichern, um die Änderungen an der Tabelle Kontakte zu speichern.
Abbildung 03: Erstellen einer Datenbanktabellenbeziehung (Klicken Sie hier, um das bild in voller Größe anzuzeigen)
Abbildung 04: Angeben von Tabellenbeziehungen(Klicken Sie hier, um das bild in voller Größe anzuzeigen)
Aktualisieren des Datenmodells
Als Nächstes müssen wir unser Datenmodell aktualisieren, um die neue Datenbanktabelle darzustellen. Führen Sie die folgenden Schritte aus:
- Doppelklicken Sie im Ordner Models auf die Datei ContactManagerModel.edmx, um die Entität Designer zu öffnen.
- Klicken Sie mit der rechten Maustaste auf die Designer Oberfläche, und wählen Sie die Menüoption Modell aus Datenbank aktualisieren aus.
- Wählen Sie im Update-Assistenten die Tabelle Gruppen aus, und klicken Sie auf die Schaltfläche Fertig stellen (siehe Abbildung 5).
- Klicken Sie mit der rechten Maustaste auf die Entität Gruppen, und wählen Sie die Menüoption Umbenennen aus. Ändern Sie den Namen der Entität Groups in Group (singular).
- Klicken Sie mit der rechten Maustaste auf die Navigationseigenschaft Groups, die unten in der Entität Contact angezeigt wird. Ändern Sie den Namen der Navigationseigenschaft Groups in Group (singular).
Abbildung 05: Aktualisieren eines Entity Framework-Modells aus der Datenbank(Klicken Sie hier, um das bild in voller Größe anzuzeigen)
Nachdem Sie diese Schritte ausgeführt haben, stellt Ihr Datenmodell sowohl die Tabellen Kontakte als auch Gruppen dar. Die Entität Designer sollte beide Entitäten anzeigen (siehe Abbildung 6).
Abbildung 06: Entität Designer, die Gruppe und Kontakt anzeigt(Klicken Sie hier, um das bild in voller Größe anzuzeigen)
Erstellen unserer Repositoryklassen
Als Nächstes müssen wir unsere Repositoryklasse implementieren. Im Laufe dieser Iteration haben wir der IContactManagerRepository-Schnittstelle mehrere neue Methoden hinzugefügt, während wir Code schreiben, um unsere Komponententests zu erfüllen. Die endgültige Version der IContactManagerRepository-Schnittstelle ist in Listing 14 enthalten.
Eintrag 14: Models\IContactManagerRepository.cs
using System.Collections.Generic;
namespace ContactManager.Models
{
public interface IContactManagerRepository
{
// Contact methods
Contact CreateContact(int groupId, Contact contactToCreate);
void DeleteContact(Contact contactToDelete);
Contact EditContact(int groupId, Contact contactToEdit);
Contact GetContact(int id);
// Group methods
Group CreateGroup(Group groupToCreate);
IEnumerable<Group> ListGroups();
Group GetGroup(int groupId);
Group GetFirstGroup();
void DeleteGroup(Group groupToDelete);
}
}
Wir haben keine der Methoden implementiert, die sich auf die Arbeit mit Kontaktgruppen beziehen. Derzeit verfügt die EntityContactManagerRepository-Klasse über Stubmethoden für jede der Kontaktgruppenmethoden, die in der IContactManagerRepository-Schnittstelle aufgeführt sind. Beispielsweise sieht die ListGroups()-Methode derzeit wie folgt aus:
public IEnumerable<Group> ListGroups()
{
throw new NotImplementedException();
}
Die Stubmethoden ermöglichten es uns, unsere Anwendung zu kompilieren und die Komponententests zu bestehen. Jetzt ist es jedoch an der Zeit, diese Methoden tatsächlich zu implementieren. Die endgültige Version der EntityContactManagerRepository-Klasse ist in Listing 13 enthalten.
Eintrag 13: Models\EntityContactManagerRepository.cs
using System.Collections.Generic;
using System.Linq;
using System;
namespace ContactManager.Models
{
public class EntityContactManagerRepository : ContactManager.Models.IContactManagerRepository
{
private ContactManagerDBEntities _entities = new ContactManagerDBEntities();
// Contact methods
public Contact GetContact(int id)
{
return (from c in _entities.ContactSet.Include("Group")
where c.Id == id
select c).FirstOrDefault();
}
public Contact CreateContact(int groupId, Contact contactToCreate)
{
// Associate group with contact
contactToCreate.Group = GetGroup(groupId);
// Save new contact
_entities.AddToContactSet(contactToCreate);
_entities.SaveChanges();
return contactToCreate;
}
public Contact EditContact(int groupId, Contact contactToEdit)
{
// Get original contact
var originalContact = GetContact(contactToEdit.Id);
// Update with new group
originalContact.Group = GetGroup(groupId);
// Save changes
_entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);
_entities.SaveChanges();
return contactToEdit;
}
public void DeleteContact(Contact contactToDelete)
{
var originalContact = GetContact(contactToDelete.Id);
_entities.DeleteObject(originalContact);
_entities.SaveChanges();
}
public Group CreateGroup(Group groupToCreate)
{
_entities.AddToGroupSet(groupToCreate);
_entities.SaveChanges();
return groupToCreate;
}
// Group Methods
public IEnumerable<Group> ListGroups()
{
return _entities.GroupSet.ToList();
}
public Group GetFirstGroup()
{
return _entities.GroupSet.Include("Contacts").FirstOrDefault();
}
public Group GetGroup(int id)
{
return (from g in _entities.GroupSet.Include("Contacts")
where g.Id == id
select g).FirstOrDefault();
}
public void DeleteGroup(Group groupToDelete)
{
var originalGroup = GetGroup(groupToDelete.Id);
_entities.DeleteObject(originalGroup);
_entities.SaveChanges();
}
}
}
Erstellen der Ansichten
ASP.NET MVC-Anwendung, wenn Sie die Standard-ASP.NET-Ansichts-Engine verwenden. Sie erstellen also keine Ansichten als Reaktion auf einen bestimmten Komponententest. Da eine Anwendung ohne Ansichten jedoch nutzlos wäre, können wir diese Iteration nicht abschließen, ohne die in der Contact Manager-Anwendung enthaltenen Ansichten zu erstellen und zu ändern.
Zum Verwalten von Kontaktgruppen müssen die folgenden neuen Ansichten erstellt werden (siehe Abbildung 7):
- Views\Group\Index.aspx– Zeigt die Liste der Kontaktgruppen an
- Views\Group\Delete.aspx: Zeigt das Bestätigungsformular zum Löschen einer Kontaktgruppe an.
Abbildung 07: Gruppenindexansicht(Klicken Sie hier, um das bild in voller Größe anzuzeigen)
Wir müssen die folgenden vorhandenen Ansichten so ändern, dass sie Kontaktgruppen enthalten:
- Ansichten\Home\Create.aspx
- Ansichten\Home\Edit.aspx
- Ansichten\Home\Index.aspx
Sie können die geänderten Ansichten sehen, indem Sie sich die Visual Studio-Anwendung ansehen, die dieses Tutorial begleitet. Abbildung 8 veranschaulicht beispielsweise die Kontaktindexansicht.
Abbildung 08: Kontaktindexansicht(Klicken Sie hier, um das bild in voller Größe anzuzeigen)
Zusammenfassung
In dieser Iteration haben wir unserer Contact Manager-Anwendung neue Funktionen hinzugefügt, indem wir eine testgesteuerte Entwicklungsanwendungsentwurfsmethodik anwenden. Wir haben zunächst eine Reihe von User Storys erstellt. Wir haben eine Reihe von Komponententests erstellt, die den Anforderungen der User Storys entsprechen. Schließlich haben wir gerade genug Code geschrieben, um die in den Komponententests ausgedrückten Anforderungen zu erfüllen.
Nachdem wir genügend Code geschrieben haben, um die anforderungen der Komponententests zu erfüllen, haben wir unsere Datenbank und ansichten aktualisiert. Wir haben unserer Datenbank eine neue Tabelle Groups hinzugefügt und unser Entity Framework-Datenmodell aktualisiert. Außerdem haben wir eine Reihe von Ansichten erstellt und geändert.
In der nächsten Iteration - der letzten Iteration - schreiben wir unsere Anwendung neu, um ajax zu nutzen. Durch die Nutzung von Ajax verbessern wir die Reaktionsfähigkeit und Leistung der Contact Manager-Anwendung.