Freigeben über


Iteration 6 – Verwenden der testgesteuerten Entwicklung (VB)

von Microsoft

Code herunterladen

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 (VB)

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):

  1. Schreiben eines Komponententests, bei dem ein Fehler auftritt (Rot)
  2. Schreiben von Code, der den Komponententest besteht (Grün)
  3. 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:

  1. Benutzer können eine Liste von Kontaktgruppen anzeigen.
  2. Der Benutzer kann eine neue Kontaktgruppe erstellen.
  3. Der Benutzer kann eine vorhandene Kontaktgruppe löschen.
  4. Der Benutzer kann beim Erstellen eines neuen Kontakts eine Kontaktgruppe auswählen.
  5. Der Benutzer kann eine Kontaktgruppe auswählen, wenn er einen vorhandenen Kontakt bearbeitet.
  6. In der Indexansicht wird eine Liste von Kontaktgruppen angezeigt.
  7. 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 beispielsweise entscheiden, dass das Erstellen einer neuen Kontaktgruppe eine Überprüfung erfordern sollte. Das Übermitteln einer Kontaktgruppe ohne Namen sollte einen Überprüfungsfehler zurückgeben.

Nachdem Sie eine Liste mit Benutzergeschichten 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 Benutzergeschichte 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 auswählen und die Vorlage Komponententest auswählen (siehe Abbildung 1). Nennen Sie den neuen Komponententest GroupControllerTest.vb, und klicken Sie auf die Schaltfläche OK .

Hinzufügen des GroupControllerTest-Komponententests

Abbildung 01: Hinzufügen des GroupControllerTest-Komponententests(Klicken Sie hier, um das vollständige Bild anzuzeigen)

Unser erster Komponententest ist in Listing 1 enthalten. Dieser Test überprüft, ob die Index()-Methode des Gruppencontrollers eine Gruppe von Gruppen zurückgibt. Der Test überprüft, ob eine Sammlung von Gruppen in Ansichtsdaten zurückgegeben wird.

Listing 1 – Controller\GroupControllerTest.vb

Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports System.Web.Mvc

<TestClass()> _
Public Class GroupControllerTest

    <TestMethod()> _
    Public Sub Index()
        ' Arrange
        Dim controller = New GroupController()

        ' Act
        Dim result = CType(controller.Index(), ViewResult)

        ' Assert
        Assert.IsInstanceOfType(result.ViewData.Model, GetType(IEnumerable(Of Group)))
    End Sub
End Class

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 minimum an Code, der zum Bestehen des Komponententests erforderlich ist. Die Index()-Aktion gibt eine statisch codierte Liste von Gruppen zurück (die Group-Klasse wird in Listing 3 definiert).

Eintrag 2: Controller\GroupController.vb

Public Class GroupController
    Inherits System.Web.Mvc.Controller

    Function Index() As ActionResult
        Dim groups = new List(Of Group)
        Return View(groups)
    End Function

End Class

Eintrag 3: Models\Group.vb

Public Class Group

End Class

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.

Erfolg!

Abbildung 02: Erfolg! (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Erstellen von Kontaktgruppen

Nun 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 zum Ausdruck bringen.

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. Mit anderen Worten, wenn ich eine neue Gruppe erstelle, sollte ich in der Lage sein, die neue Gruppe aus der Liste der Von der Index()-Methode zurückgegebenen Gruppen zurückzuholen.

Eintrag 4: Controller\GroupControllerTest.vb

<TestMethod> _
Public Sub Create()
    ' Arrange
    Dim controller = New GroupController()

    ' Act
    Dim groupToCreate = New Group()
    controller.Create(groupToCreate)

    ' Assert
    Dim result = CType(controller.Index(), ViewResult)
    Dim groups = CType(result.ViewData.Model, IEnumerable(Of Group))
    CollectionAssert.Contains(groups.ToList(), groupToCreate)
End Sub

Der Test in Listing 4 ruft die Create()-Methode des Gruppencontrollers mit einem neuen Kontakt Group auf. Als Nächstes wird im Test überprüft, ob beim Aufrufen der Index()-Methode des Gruppencontrollers die neue Group in Ansichtsdaten zurückgegeben wird.

Der geänderte Gruppencontroller in Listing 5 enthält das minimum an Änderungen, die erforderlich sind, um den neuen Test zu bestehen.

Eintrag 5: Controller\GroupController.vb

Public Class GroupController
Inherits Controller

Private _groups As IList(Of Group) = New List(Of Group)()

Public Function Index() As ActionResult
    Return View(_groups)
End Function

Public Function Create(ByVal groupToCreate As Group) As ActionResult
    _groups.Add(groupToCreate)
    Return RedirectToAction("Index")

End Function
End Class

Der Gruppencontroller in Listing 5 verfügt über eine neue Create()-Aktion. Diese Aktion fügt einer Auflistung von Gruppen eine Gruppe hinzu. Beachten Sie, dass die Index()-Aktion geändert wurde, um den Inhalt der Sammlung von Gruppen zurückzugeben.

Erneut 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, bestehen alle Komponententests.

Hinzufügen der Validierung

Diese Anforderung wurde im User Story nicht explizit angegeben. Es ist jedoch sinnvoll, zu verlangen, 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.

Eintrag 6: Controller\GroupControllerTest.vb

<TestMethod> _
Public Sub CreateRequiredName()
    ' Arrange
    Dim controller = New GroupController()

    ' Act
    Dim groupToCreate As New Group()
    groupToCreate.Name = String.Empty
    Dim result = CType(controller.Create(groupToCreate), ViewResult)

    ' Assert
    Dim [error] = result.ViewData.ModelState("Name").Errors(0)
    Assert.AreEqual("Name is required.", [error].ErrorMessage)
End Sub

Um diesen Test zu erfüllen, müssen wir unserer Group-Klasse eine Name-Eigenschaft hinzufügen (siehe Eintrag 7). Darüber hinaus müssen wir der Create()-Aktion des Gruppencontrollers ein kleines Bisschen Validierungslogik hinzufügen (siehe Eintrag 8).

Eintrag 7: Models\Group.vb

Public Class Group

    Private _name As String

    Public Property Name() As String
    Get
        Return _name
    End Get
    Set(ByVal value As String)
        _name = value
    End Set
End Property

End Class

Eintrag 8: Controller\GroupController.vb

Public Function Create(ByVal groupToCreate As Group) As ActionResult
    ' Validation logic
    If groupToCreate.Name.Trim().Length = 0 Then
    ModelState.AddModelError("Name", "Name is required.")
    Return View("Create")
    End If

    ' Database logic
    _groups.Add(groupToCreate)
    Return RedirectToAction("Index")
End Function

Beachten Sie, dass die Gruppierungscontrolleraktion Create() jetzt sowohl Die Validierung als auch Datenbanklogik enthält. Derzeit besteht die vom Gruppencontroller verwendete Datenbank aus nichts weiter als einer In-Memory-Sammlung.

Zeit bis zur Umgestaltung

Der dritte Schritt in Rot/Grün/Refactor ist der Refactor-Teil. An diesem Punkt müssen wir uns von unserem Code zurückhalten 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 über die beste Möglichkeit zur Implementierung von Softwareentwurfsprinzipien und -mustern gedanken machen.

Wir können unseren Code in jeder Weise ändern, die wir wählen, um den Entwurf des Codes zu verbessern. Wir verfügen über ein Sicherheitsnetz von Komponententests, die verhindern, dass wir vorhandene Funktionen unterbrechen.

Im Moment ist unser Konzerncontroller aus Sicht eines guten Softwaredesigns ein Durcheinander. Der Gruppencontroller enthält ein Durcheinander an Validierungs- und Datenzugriffscode. Um eine Verletzung des Grundsatzes der einheitlichen Verantwortung zu vermeiden, müssen wir diese Belange in verschiedene Klassen unterteilen.

Unsere umgestaltete Gruppencontrollerklasse ist in Listing 9 enthalten. Der Controller wurde so geändert, dass er die ContactManager-Dienstebene verwendet. Dies ist dieselbe Dienstebene, die wir mit dem Kontaktcontroller verwenden.

List 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.

Eintrag 9: Controller\GroupController.vb

Public Class GroupController
Inherits Controller

Private _service As IContactManagerService

Public Sub New()
    _service = New ContactManagerService(New ModelStateWrapper(Me.ModelState))

End Sub

Public Sub New(ByVal service As IContactManagerService)
    _service = service
End Sub

Public Function Index() As ActionResult
    Return View(_service.ListGroups())
End Function


Public Function Create(ByVal groupToCreate As Group) As ActionResult
    If _service.CreateGroup(groupToCreate) Then
        Return RedirectToAction("Index")
    End If
    Return View("Create")
End Function

End Class

Eintrag 10 : Controller\ContactManagerService.vb

Public Function ValidateGroup(ByVal groupToValidate As Group) As Boolean
If groupToValidate.Name.Trim().Length = 0 Then
    _validationDictionary.AddError("Name", "Name is required.")
End If
Return _validationDictionary.IsValid
End Function

Public Function CreateGroup(ByVal groupToCreate As Group) As Boolean Implements IContactManagerService.CreateGroup
    ' Validation logic
    If Not ValidateGroup(groupToCreate) Then
        Return False
    End If

    ' Database logic
    Try
        _repository.CreateGroup(groupToCreate)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerService.ListGroups
    Return _repository.ListGroups()
End Function

Eintrag 11 : Controller\FakeContactManagerRepository.vb

Public Class FakeContactManagerRepository
Implements IContactManagerRepository

Private _groups As IList(Of Group) = New List(Of Group)()

#Region "IContactManagerRepository Members"

' Group methods

Public Function CreateGroup(ByVal groupToCreate As Group) As Group Implements IContactManagerRepository.CreateGroup
    _groups.Add(groupToCreate)
    Return groupToCreate
End Function

Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups
    Return _groups
End Function

' Contact methods

Public Function CreateContact(ByVal contactToCreate As Contact) As Contact Implements IContactManagerRepository.CreateContact
    Throw New NotImplementedException()
End Function

Public Sub DeleteContact(ByVal contactToDelete As Contact) Implements IContactManagerRepository.DeleteContact
    Throw New NotImplementedException()
End Sub

Public Function EditContact(ByVal contactToEdit As Contact) As Contact Implements IContactManagerRepository.EditContact
    Throw New NotImplementedException()
End Function

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerRepository.GetContact
    Throw New NotImplementedException()
End Function

Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerRepository.ListContacts
    Throw New NotImplementedException()
End Function

#End Region
End Class

Um die IContactManagerRepository-Schnittstelle zu ändern, müssen die Methoden CreateGroup() und ListGroups() in der EntityContactManagerRepository-Klasse implementiert werden. Die faulste und schnellste Möglichkeit, dies zu tun, ist das Hinzufügen von Stubmethoden, die wie folgt aussehen:

Public Function CreateGroup(groupToCreate As Group) As Group Implements IContactManagerRepository.CreateGroup

    throw New NotImplementedException()

End Function 

Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups

    throw New NotImplementedException()

End Function

Schließlich erfordern diese Änderungen am Design 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.

Eintrag 12: Controller\GroupControllerTest.vb

Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports System.Web.Mvc

<TestClass()> _
Public Class GroupControllerTest

    Private _repository As IContactManagerRepository
    Private _modelState As ModelStateDictionary
    Private _service As IContactManagerService

    <TestInitialize()> _
    Public Sub Initialize()
        _repository = New FakeContactManagerRepository()
        _modelState = New ModelStateDictionary()
        _service = New ContactManagerService(New ModelStateWrapper(_modelState), _repository)
    End Sub

    <TestMethod()> _
    Public Sub Index()
        ' Arrange
        Dim controller = New GroupController(_service)

        ' Act
        Dim result = CType(controller.Index(), ViewResult)

        ' Assert
        Assert.IsInstanceOfType(result.ViewData.Model, GetType(IEnumerable(Of Group)))
    End Sub

    <TestMethod()> _
    Public Sub Create()
        ' Arrange
        Dim controller = New GroupController(_service)

        ' Act
        Dim groupToCreate = New Group()
        groupToCreate.Name = "Business"
        controller.Create(groupToCreate)

        ' Assert
        Dim result = CType(controller.Index(), ViewResult)
        Dim groups = CType(result.ViewData.Model, IEnumerable(Of Group))
        CollectionAssert.Contains(groups.ToList(), groupToCreate)
    End Sub

    <TestMethod()> _
    Public Sub CreateRequiredName()
        ' Arrange
        Dim controller = New GroupController(_service)

        ' Act
        Dim groupToCreate = New Group()
        groupToCreate.Name = String.Empty
        Dim result = CType(controller.Create(groupToCreate), ViewResult)

        ' Assert
        Dim nameError = _modelState("Name").Errors(0)
        Assert.AreEqual("Name is required.", nameError.ErrorMessage)
    End Sub

End Class

Nachdem wir alle diese Änderungen vorgenommen haben, werden erneut alle Komponententests erfolgreich bestanden. Wir haben den gesamten Zyklus von Rot/Grün/Refactor 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 beinhaltet die Wiederholung desselben Zyklus von Rot/Grün/Refactor.

Ändern unserer Datenbank

Leider ist unsere Arbeit nicht erledigt, obwohl wir alle Anforderungen unserer Komponententests erfüllt haben. Wir müssen unsere Datenbank noch ändern.

Wir müssen eine neue Gruppendatenbanktabelle erstellen. Führen Sie die folgenden Schritte aus:

  1. 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.
  2. Geben Sie die beiden unten im Designer Tabelle beschriebenen Spalten ein.
  3. Markieren Sie die Id-Spalte als Primärschlüssel und Identitätsspalte.
  4. 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:

  1. Klicken Sie mit der rechten Maustaste auf die Tabelle Kontakte, und wählen Sie die Menüoption Tabellendaten anzeigen aus.
  2. 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:

  1. Doppelklicken Sie im Fenster Server Explorer auf die Tabelle Kontakte, um die tabelle Designer zu öffnen.
  2. Fügen Sie der Tabelle Kontakte eine neue ganzzahlige Spalte mit dem Namen GroupId hinzu.
  3. Klicken Sie auf die Schaltfläche Beziehung, um das Dialogfeld Fremdschlüsselbeziehungen zu öffnen (siehe Abbildung 3).
  4. Klicken Sie auf die Schaltfläche Hinzufügen.
  5. Klicken Sie auf die Schaltfläche mit den Auslassungspunkten, die neben der Schaltfläche Tabelle und Spaltenspezifikation angezeigt wird.
  6. 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".
  7. Wählen Sie unter INSERT- und UPDATE-Spezifikation den Wert Cascade für Regel löschen aus.
  8. Klicken Sie auf die Schaltfläche Schließen, um das Dialogfeld Fremdschlüsselbeziehungen zu schließen.
  9. Klicken Sie auf die Schaltfläche Speichern, um die Änderungen an der Tabelle Kontakte zu speichern.

Erstellen einer Datenbanktabellenbeziehung

Abbildung 03: Erstellen einer Datenbanktabellenbeziehung (Klicken Sie hier, um das bild in voller Größe anzuzeigen)

Angeben von Tabellenbeziehungen

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:

  1. Doppelklicken Sie im Ordner Models auf die Datei ContactManagerModel.edmx, um die Entität Designer zu öffnen.
  2. Klicken Sie mit der rechten Maustaste auf die Designer Oberfläche, und wählen Sie die Menüoption Modell aus Datenbank aktualisieren aus.
  3. Wählen Sie im Update-Assistenten die Tabelle Gruppen aus, und klicken Sie auf die Schaltfläche Fertig stellen (siehe Abbildung 5).
  4. 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).
  5. 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).

Aktualisieren eines Entity Framework-Modells aus der Datenbank

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).

Entität Designer, die Gruppe und Kontakt anzeigt

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.vb

Public Interface IContactManagerRepository
' Contact methods
Function CreateContact(ByVal groupId As Integer, ByVal contactToCreate As Contact) As Contact
Sub DeleteContact(ByVal contactToDelete As Contact)
Function EditContact(ByVal groupId As Integer, ByVal contactToEdit As Contact) As Contact
Function GetContact(ByVal id As Integer) As Contact

' Group methods
Function CreateGroup(ByVal groupToCreate As Group) As Group
Function ListGroups() As IEnumerable(Of Group)
Function GetGroup(ByVal groupId As Integer) As Group
Function GetFirstGroup() As Group
Sub DeleteGroup(ByVal groupToDelete As Group)

End Interface

Wir haben keine der Methoden für die Arbeit mit Kontaktgruppen in unserer echten EntityContactManagerRepository-Klasse implementiert. 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 Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups

    throw New NotImplementedException()

End Function

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.vb

Public Class EntityContactManagerRepository
Implements IContactManagerRepository

Private _entities As New ContactManagerDBEntities()

' Contact methods

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerRepository.GetContact
    Return (From c In _entities.ContactSet.Include("Group") _
            Where c.Id = id _
            Select c).FirstOrDefault()
End Function

Public Function CreateContact(ByVal groupId As Integer, ByVal contactToCreate As Contact) As Contact Implements IContactManagerRepository.CreateContact
    ' Associate group with contact
    contactToCreate.Group = GetGroup(groupId)

    ' Save new contact
    _entities.AddToContactSet(contactToCreate)
    _entities.SaveChanges()
    Return contactToCreate
End Function

Public Function EditContact(ByVal groupId As Integer, ByVal contactToEdit As Contact) As Contact Implements IContactManagerRepository.EditContact
    ' Get original contact
    Dim originalContact = GetContact(contactToEdit.Id)

    ' Update with new group
    originalContact.Group = GetGroup(groupId)

    ' Save changes
    _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit)
    _entities.SaveChanges()
    Return contactToEdit
End Function

Public Sub DeleteContact(ByVal contactToDelete As Contact) Implements IContactManagerRepository.DeleteContact 
    Dim originalContact = GetContact(contactToDelete.Id)
    _entities.DeleteObject(originalContact)
    _entities.SaveChanges()
End Sub

    ' Group methods

Public Function CreateGroup(ByVal groupToCreate As Group) As Group Implements IContactManagerRepository.CreateGroup 
    _entities.AddToGroupSet(groupToCreate)
    _entities.SaveChanges()
    Return groupToCreate
End Function

Public Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups
    Return _entities.GroupSet.ToList()
End Function

Public Function GetFirstGroup() As Group Implements IContactManagerRepository.GetFirstGroup
    Return _entities.GroupSet.Include("Contacts").FirstOrDefault()
End Function

Public Function GetGroup(ByVal id As Integer) As Group Implements IContactManagerRepository.GetGroup
    Return (From g In _entities.GroupSet.Include("Contacts") _
            Where g.Id = id _
            Select g).FirstOrDefault()
End Function

Public Sub DeleteGroup(ByVal groupToDelete As Group) Implements IContactManagerRepository.DeleteGroup
    Dim originalGroup = GetGroup(groupToDelete.Id)
    _entities.DeleteObject(originalGroup)
    _entities.SaveChanges()
End Sub

End Class

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.

Die Gruppenindexansicht

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.

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.