Partager via


Itération #6 : Utiliser le développement piloté par les tests (VB)

par Microsoft

Télécharger le code

Dans cette sixième itération, nous ajoutons de nouvelles fonctionnalités à notre application en écrivant d’abord des tests unitaires et en écrivant du code sur les tests unitaires. Dans cette itération, nous ajoutons des groupes de contacts.

Génération d’une application Gestion des contacts ASP.NET MVC (VB)

Dans cette série de tutoriels, nous créons une application de gestion des contacts complète du début à la fin. L’application Gestionnaire de contacts vous permet de stocker des informations de contact (noms, numéros de téléphone et adresses e-mail) pour une liste de personnes.

Nous créons l’application sur plusieurs itérations. À chaque itération, nous améliorons progressivement l’application. L’objectif de cette approche à itérations multiples est de vous permettre de comprendre la raison de chaque modification.

  • Itération #1 : créez l’application. Dans la première itération, nous créons le Gestionnaire de contacts de la manière la plus simple possible. Nous ajoutons la prise en charge des opérations de base de données de base de données : Create, Read, Update et Delete (CRUD).

  • Itération n°2 : rendre l’application agréable. Dans cette itération, nous améliorons l’apparence de l’application en modifiant la ASP.NET vue MVC par défaut master page et la feuille de style en cascade.

  • Itération n°3 - Ajouter la validation de formulaire. Dans la troisième itération, nous ajoutons la validation de formulaire de base. Nous empêcherons les utilisateurs d’envoyer un formulaire sans remplir les champs de formulaire requis. Nous validons également les adresses e-mail et les numéros de téléphone.

  • Itération n° 4 : rendre l’application faiblement couplée. Dans cette quatrième itération, nous profitons de plusieurs modèles de conception logicielle pour faciliter la maintenance et la modification de l’application Gestionnaire de contacts. Par exemple, nous refactorisons notre application pour utiliser le modèle référentiel et le modèle d’injection de dépendances.

  • Itération #5 - Créer des tests unitaires. Dans la cinquième itération, nous rendons notre application plus facile à gérer et à modifier en ajoutant des tests unitaires. Nous modélisons nos classes de modèle de données et créons des tests unitaires pour nos contrôleurs et notre logique de validation.

  • Itération n°6 - Utiliser le développement piloté par les tests. Dans cette sixième itération, nous ajoutons de nouvelles fonctionnalités à notre application en écrivant d’abord des tests unitaires et en écrivant du code sur les tests unitaires. Dans cette itération, nous ajoutons des groupes de contacts.

  • Itération n°7 - Ajouter une fonctionnalité Ajax. Dans la septième itération, nous améliorons la réactivité et les performances de notre application en ajoutant la prise en charge d’Ajax.

Cette itération

Dans l’itération précédente de l’application Gestionnaire de contacts, nous avons créé des tests unitaires pour fournir un filet de sécurité pour notre code. La motivation de la création des tests unitaires était de rendre notre code plus résilient au changement. Une fois les tests unitaires en place, nous pouvons apporter toute modification à notre code et savoir immédiatement si nous avons rompu les fonctionnalités existantes.

Dans cette itération, nous utilisons des tests unitaires dans un but totalement différent. Dans cette itération, nous utilisons des tests unitaires dans le cadre d’une philosophie de conception d’application appelée développement piloté par les tests. Lorsque vous pratiquez le développement piloté par les tests, vous écrivez d’abord des tests, puis écrivez du code sur les tests.

Plus précisément, lors de la pratique du développement piloté par les tests, vous devez effectuer trois étapes lors de la création du code (Rouge/ Vert/Refactorisation) :

  1. Écrire un test unitaire qui échoue (Rouge)
  2. Écrire du code qui réussit le test unitaire (vert)
  3. Refactoriser votre code (Refactoriser)

Tout d’abord, vous écrivez le test unitaire. Le test unitaire doit exprimer votre intention quant à la façon dont vous vous attendez à ce que votre code se comporte. Lorsque vous créez le test unitaire pour la première fois, le test unitaire doit échouer. Le test doit échouer, car vous n’avez pas encore écrit de code d’application satisfaisant le test.

Ensuite, vous écrivez juste assez de code pour que le test unitaire réussisse. L’objectif est d’écrire le code de la manière la plus paresseuse, la plus loppable et la plus rapide possible. Vous ne devez pas perdre de temps à réfléchir à l’architecture de votre application. Au lieu de cela, vous devez vous concentrer sur l’écriture de la quantité minimale de code nécessaire pour satisfaire l’intention exprimée par le test unitaire.

Enfin, une fois que vous avez écrit suffisamment de code, vous pouvez prendre du recul et prendre en compte l’architecture globale de votre application. Dans cette étape, vous réécritez (refactorisez) votre code en tirant parti des modèles de conception de logiciels, tels que le modèle de dépôt, afin que votre code soit plus facile à gérer. Vous pouvez réécrire votre code sans crainte dans cette étape, car votre code est couvert par des tests unitaires.

La pratique du développement piloté par les tests présente de nombreux avantages. Tout d’abord, le développement piloté par les tests vous oblige à vous concentrer sur du code qui doit réellement être écrit. Étant donné que vous vous concentrez constamment sur l’écriture de suffisamment de code pour réussir un test particulier, vous ne pouvez pas vous aventurer dans les mauvaises herbes et écrire des quantités massives de code que vous n’utiliserez jamais.

Deuxièmement, une méthodologie de conception « test first » vous oblige à écrire du code du point de vue de la façon dont votre code sera utilisé. En d’autres termes, lorsque vous pratiquez le développement piloté par les tests, vous écrivez constamment vos tests du point de vue de l’utilisateur. Par conséquent, le développement piloté par les tests peut aboutir à des API plus propres et plus compréhensibles.

Enfin, le développement piloté par les tests vous oblige à écrire des tests unitaires dans le cadre du processus normal d’écriture d’une application. À l’approche de l’échéance d’un projet, les tests sont généralement la première chose qui sort de la fenêtre. En revanche, lorsque vous pratiquez le développement piloté par les tests, vous êtes plus susceptible d’être vertueux sur l’écriture de tests unitaires, car le développement piloté par les tests fait des tests unitaires un élément central du processus de création d’une application.

Notes

Pour en savoir plus sur le développement piloté par les tests, je vous recommande de lire le livre de Michael Feathers Working Effective with Legacy Code.

Dans cette itération, nous ajoutons une nouvelle fonctionnalité à notre application Gestionnaire de contacts. Nous ajoutons la prise en charge des groupes de contacts. Vous pouvez utiliser des groupes de contacts pour organiser vos contacts en catégories, telles que les groupes d’amis et d’entreprise.

Nous allons ajouter cette nouvelle fonctionnalité à notre application en suivant un processus de développement piloté par les tests. Nous allons d’abord écrire nos tests unitaires et nous allons écrire tout notre code sur ces tests.

Éléments testés

Comme nous l’avons vu dans l’itération précédente, vous n’écrivez généralement pas de tests unitaires pour la logique d’accès aux données ou la logique d’affichage. Vous n’écrivez pas de tests unitaires pour la logique d’accès aux données, car l’accès à une base de données est une opération relativement lente. Vous n’écrivez pas de tests unitaires pour la logique d’affichage, car l’accès à une vue nécessite la rotation d’un serveur web, ce qui est une opération relativement lente. Vous ne devez pas écrire un test unitaire, sauf si le test peut être exécuté à plusieurs reprises très rapidement

Étant donné que le développement piloté par les tests est piloté par des tests unitaires, nous nous concentrons d’abord sur l’écriture du contrôleur et de la logique métier. Nous évitez de toucher la base de données ou les vues. Nous ne modifierons pas la base de données ou ne créerons nos vues qu’à la fin de ce tutoriel. Nous commençons par ce qui peut être testé.

Création de récits utilisateur

Lorsque vous pratiquez le développement piloté par les tests, vous commencez toujours par écrire un test. Cela soulève immédiatement la question : comment choisir le test à écrire en premier ? Pour répondre à cette question, vous devez écrire un ensemble de récits utilisateur.

Un récit utilisateur est une description très brève (généralement d’une phrase) d’une exigence logicielle. Il doit s’agir d’une description non technique d’une exigence écrite du point de vue de l’utilisateur.

Voici l’ensemble des récits utilisateur qui décrivent les fonctionnalités requises par la nouvelle fonctionnalité de groupe de contacts :

  1. L’utilisateur peut afficher une liste de groupes de contacts.
  2. L’utilisateur peut créer un groupe de contacts.
  3. L’utilisateur peut supprimer un groupe de contacts existant.
  4. L’utilisateur peut sélectionner un groupe de contacts lors de la création d’un contact.
  5. L’utilisateur peut sélectionner un groupe de contacts lors de la modification d’un contact existant.
  6. Une liste de groupes de contacts s’affiche dans la vue Index.
  7. Lorsqu’un utilisateur clique sur un groupe de contacts, une liste de contacts correspondants s’affiche.

Notez que cette liste d’histoires d’utilisateurs est entièrement compréhensible par un client. Il n’existe aucun mention de détails d’implémentation technique.

Lors du processus de création de votre application, l’ensemble des récits utilisateur peut devenir plus affiné. Vous pouvez diviser un récit utilisateur en plusieurs récits (conditions requises). Par exemple, vous pouvez décider que la création d’un groupe de contacts doit impliquer une validation. L’envoi d’un groupe de contacts sans nom doit renvoyer une erreur de validation.

Après avoir créé une liste de récits utilisateur, vous êtes prêt à écrire votre premier test unitaire. Nous allons commencer par créer un test unitaire pour afficher la liste des groupes de contacts.

Liste des groupes de contacts

Notre première histoire d’utilisateur est qu’un utilisateur doit être en mesure d’afficher une liste de groupes de contacts. Nous devons exprimer cette histoire avec un test.

Créez un test unitaire en cliquant avec le bouton droit sur le dossier Controllers dans le projet ContactManager.Tests, en sélectionnant Ajouter, Nouveau test, puis en sélectionnant le modèle Test unitaire (voir figure 1). Nommez le nouveau test unitaire GroupControllerTest.vb, puis cliquez sur le bouton OK .

Ajout du test unitaire GroupControllerTest

Figure 01 : Ajout du test unitaire GroupControllerTest (Cliquer pour afficher l’image en taille réelle)

Notre premier test unitaire est contenu dans la liste 1. Ce test vérifie que la méthode Index() du contrôleur de groupe retourne un ensemble de groupes. Le test vérifie qu’une collection de groupes est retournée dans les données de la vue.

Listing 1 - Controllers\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

Lorsque vous tapez le code pour la première fois dans listing 1 dans Visual Studio, vous obtenez un grand nombre de lignes rouges ondulées. Nous n’avons pas créé les classes GroupController ou Group.

À ce stade, nous ne pouvons même pas créer notre application et nous ne pouvons donc pas exécuter notre premier test unitaire. C’est bien. Cela compte comme un test défaillant. Par conséquent, nous avons maintenant l’autorisation de commencer à écrire du code d’application. Nous devons écrire suffisamment de code pour exécuter notre test.

La classe de contrôleur de groupe dans la liste 2 contient le strict minimum de code requis pour réussir le test unitaire. L’action Index() retourne une liste codée statiquement de groupes (la classe Group est définie dans la liste 3).

Listing 2 - Controllers\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

Listing 3 - Models\Group.vb

Public Class Group

End Class

Une fois que nous avons ajouté les classes GroupController et Group à notre projet, notre premier test unitaire se termine correctement (voir la figure 2). Nous avons effectué le travail minimum nécessaire pour réussir le test. Il est temps de célébrer.

Succès!

Figure 02 : Réussite ! (Cliquez pour afficher l’image en taille réelle)

Création de groupes de contacts

Nous pouvons maintenant passer à la deuxième histoire utilisateur. Nous devons pouvoir créer des groupes de contacts. Nous devons exprimer cette intention par un test.

Le test de la liste 4 vérifie que l’appel de la méthode Create() avec un nouveau groupe ajoute le groupe à la liste de groupes retournée par la méthode Index(). En d’autres termes, si je crée un groupe, je devrais pouvoir récupérer le nouveau groupe à partir de la liste des groupes retournée par la méthode Index().

Listing 4 - Controllers\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

Le test de la liste 4 appelle la méthode Create() du contrôleur de groupe avec un nouveau groupe de contacts. Ensuite, le test vérifie que l’appel de la méthode Index() du contrôleur de groupe renvoie le nouveau groupe dans les données de la vue.

Le contrôleur de groupe modifié dans la liste 5 contient le strict minimum de modifications requises pour réussir le nouveau test.

Listing 5 - Controllers\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

Le contrôleur de groupe dans la liste 5 a une nouvelle action Créer(). Cette action ajoute un groupe à une collection de groupes. Notez que l’action Index() a été modifiée pour retourner le contenu de la collection de Groupes.

Une fois de plus, nous avons effectué le strict minimum de travail nécessaire pour réussir le test unitaire. Une fois que nous avons apporté ces modifications au contrôleur de groupe, tous nos tests unitaires réussissent.

Ajout de la validation

Cette exigence n’a pas été explicitement énoncée dans le récit utilisateur. Toutefois, il est raisonnable d’exiger qu’un groupe ait un nom. Sinon, l’organisation des contacts en groupes ne serait pas très utile.

La liste 6 contient un nouveau test qui exprime cette intention. Ce test vérifie que la tentative de création d’un groupe sans fournir de nom entraîne un message d’erreur de validation dans l’état du modèle.

Listing 6 - Controllers\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

Pour satisfaire ce test, nous devons ajouter une propriété Name à notre classe Group (voir Référencement 7). En outre, nous devons ajouter un petit peu de logique de validation à notre action Créer() du contrôleur de groupe (voir Listing 8).

Listing 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

Listing 8 - Controllers\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

Notez que l’action Créer() du contrôleur de groupe contient désormais à la fois la logique de validation et la logique de base de données. Actuellement, la base de données utilisée par le contrôleur de groupe se compose d’une collection en mémoire.

Temps de refactorisation

La troisième étape en rouge/vert/refactorisation est la partie Refactorisation. À ce stade, nous devons prendre du recul sur notre code et réfléchir à la façon dont nous pouvons refactoriser notre application pour améliorer sa conception. L’étape refactorisation est l’étape à laquelle nous réfléchissons dur sur la meilleure façon d’implémenter des principes et des modèles de conception logicielle.

Nous sommes libres de modifier notre code de la manière que nous choisissons pour améliorer la conception du code. Nous disposons d’un filet de sécurité de tests unitaires qui nous empêchent de rompre les fonctionnalités existantes.

À l’heure actuelle, notre contrôleur de groupe est un gâchis du point de vue d’une bonne conception logicielle. Le contrôleur de groupe contient un désordre enchevêtré de code d’accès aux données et de validation. Pour éviter de violer le principe de responsabilité unique, nous devons séparer ces préoccupations en différentes classes.

Notre classe de contrôleur de groupe refactorisé est contenue dans la liste 9. Le contrôleur a été modifié pour utiliser la couche de service ContactManager. Il s’agit de la même couche de service que celle que nous utilisons avec le contrôleur de contact.

La liste 10 contient les nouvelles méthodes ajoutées à la couche de service ContactManager pour prendre en charge la validation, la liste et la création de groupes. L’interface IContactManagerService a été mise à jour pour inclure les nouvelles méthodes.

La liste 11 contient une nouvelle classe FakeContactManagerRepository qui implémente l’interface IContactManagerRepository. Contrairement à la classe EntityContactManagerRepository qui implémente également l’interface IContactManagerRepository, notre nouvelle classe FakeContactManagerRepository ne communique pas avec la base de données. La classe FakeContactManagerRepository utilise une collection en mémoire comme proxy pour la base de données. Nous allons utiliser cette classe dans nos tests unitaires comme une fausse couche de dépôt.

Listing 9 - Controllers\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

Listing 10 - Controllers\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

Listing 11 - Controllers\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

La modification de l’interface IContactManagerRepository nécessite d’implémenter les méthodes CreateGroup() et ListGroups() dans la classe EntityContactManagerRepository. Le moyen le plus paresseux et le plus rapide d’y parvenir consiste à ajouter des méthodes stub qui ressemblent à ceci :

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

Enfin, ces modifications apportées à la conception de notre application nous obligent à apporter des modifications à nos tests unitaires. Nous devons maintenant utiliser FakeContactManagerRepository lors de l’exécution des tests unitaires. La classe GroupControllerTest mise à jour est contenue dans la liste 12.

Listing 12 - Controllers\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

Une fois que nous avons apporté toutes ces modifications, une fois de plus, tous nos tests unitaires réussissent. Nous avons terminé l’intégralité du cycle rouge/vert/refactorisation. Nous avons implémenté les deux premiers récits utilisateur. Nous avons maintenant des tests unitaires de prise en charge pour les exigences exprimées dans les récits utilisateur. L’implémentation du reste des récits utilisateur implique de répéter le même cycle rouge/vert/refactorisation.

Modification de notre base de données

Malheureusement, même si nous avons satisfait à toutes les exigences exprimées par nos tests unitaires, notre travail n’est pas fait. Nous devons toujours modifier notre base de données.

Nous devons créer une table de base de données de groupe. Procédez comme suit :

  1. Dans la fenêtre Server Explorer, cliquez avec le bouton droit sur le dossier Tables et sélectionnez l’option de menu Ajouter une nouvelle table.
  2. Entrez les deux colonnes décrites ci-dessous dans la table Designer.
  3. Marquez la colonne Id en tant que clé primaire et colonne Identité.
  4. Enregistrez la nouvelle table avec le nom Groupes en cliquant sur l’icône de la disquette.

Nom de la colonne Type de données Null autorisé
Id int False
Nom nvarchar(50) False

Ensuite, nous devons supprimer toutes les données de la table Contacts (sinon, nous ne pourrons pas créer une relation entre les tables Contacts et Groupes). Procédez comme suit :

  1. Cliquez avec le bouton droit sur la table Contacts et sélectionnez l’option de menu Afficher les données du tableau.
  2. Supprimez toutes les lignes.

Ensuite, nous devons définir une relation entre la table de base de données Groups et la table de base de données Contacts existante. Procédez comme suit :

  1. Double-cliquez sur la table Contacts dans la fenêtre Server Explorer pour ouvrir le Designer table.
  2. Ajoutez une nouvelle colonne entière à la table Contacts nommée GroupId.
  3. Cliquez sur le bouton Relation pour ouvrir la boîte de dialogue Relations de clé étrangère (voir figure 3).
  4. Cliquez sur le bouton Ajouter.
  5. Cliquez sur le bouton de sélection qui s’affiche en regard du bouton Spécification de la table et des colonnes.
  6. Dans la boîte de dialogue Tables et colonnes, sélectionnez Groupes comme table de clé primaire et Id comme colonne de clé primaire. Sélectionnez Contacts comme table de clé étrangère et GroupId comme colonne de clé étrangère (voir figure 4). Cliquez sur le bouton OK.
  7. Sous INSERT et UPDATE Specification, sélectionnez la valeur Cascade pour Supprimer la règle.
  8. Cliquez sur le bouton Fermer pour fermer la boîte de dialogue Relations de clé étrangère.
  9. Cliquez sur le bouton Enregistrer pour enregistrer les modifications apportées à la table Contacts.

Création d’une relation de table de base de données

Figure 03 : Création d’une relation de table de base de données (cliquer pour afficher l’image de taille réelle)

Spécification de relations de table

Figure 04 : Spécification de relations de table (Cliquez pour afficher l’image de taille réelle)

Mise à jour de notre modèle de données

Ensuite, nous devons mettre à jour notre modèle de données pour représenter la nouvelle table de base de données. Procédez comme suit :

  1. Double-cliquez sur le fichier ContactManagerModel.edmx dans le dossier Models pour ouvrir le Designer d’entité.
  2. Cliquez avec le bouton droit sur l’Designer surface et sélectionnez l’option de menu Mettre à jour le modèle à partir de la base de données.
  3. Dans l’Assistant Mise à jour, sélectionnez la table Groupes, puis cliquez sur le bouton Terminer (voir figure 5).
  4. Cliquez avec le bouton droit sur l’entité Groupes et sélectionnez l’option de menu Renommer. Remplacez le nom de l’entité Groups par Group (singular).
  5. Cliquez avec le bouton droit sur la propriété de navigation Groups qui s’affiche en bas de l’entité Contact. Remplacez le nom de la propriété de navigation Groups par Group (singular).

Mise à jour d’un modèle Entity Framework à partir de la base de données

Figure 05 : Mise à jour d’un modèle Entity Framework à partir de la base de données (Cliquez pour afficher l’image en taille réelle)

Une fois ces étapes terminées, votre modèle de données représente les tables Contacts et Groupes. Le Designer d’entité doit afficher les deux entités (voir figure 6).

Designer d’entité affichant le groupe et le contact

Figure 06 : Entité Designer affichage du groupe et du contact (cliquer pour afficher l’image en taille réelle)

Création de nos classes de référentiel

Ensuite, nous devons implémenter notre classe de dépôt. Au cours de cette itération, nous avons ajouté plusieurs nouvelles méthodes à l’interface IContactManagerRepository lors de l’écriture de code pour satisfaire nos tests unitaires. La version finale de l’interface IContactManagerRepository est contenue dans listing 14.

Listing 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

Nous n’avons implémenté aucune des méthodes liées à l’utilisation des groupes de contacts dans notre classe EntityContactManagerRepository réelle. Actuellement, la classe EntityContactManagerRepository a des méthodes stub pour chacune des méthodes de groupe de contacts répertoriées dans l’interface IContactManagerRepository. Par exemple, la méthode ListGroups() ressemble actuellement à ceci :

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

    throw New NotImplementedException()

End Function

Les méthodes stub nous ont permis de compiler notre application et de réussir les tests unitaires. Cependant, il est maintenant temps d’implémenter réellement ces méthodes. La version finale de la classe EntityContactManagerRepository est contenue dans listing 13.

Listing 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

Création des vues

ASP.NET application MVC lorsque vous utilisez le moteur d’affichage ASP.NET par défaut. Vous ne créez donc pas de vues en réponse à un test unitaire particulier. Toutefois, étant donné qu’une application serait inutile sans affichages, nous ne pouvons pas effectuer cette itération sans créer et modifier les vues contenues dans l’application Gestionnaire de contacts.

Nous devons créer les vues suivantes pour la gestion des groupes de contacts (voir figure 7) :

  • Views\Group\Index.aspx - Affiche la liste des groupes de contacts
  • Views\Group\Delete.aspx - Affiche le formulaire de confirmation de suppression d’un groupe de contacts

Affichage Index de groupe

Figure 07 : Affichage Index de groupe (cliquer pour afficher l’image en taille réelle)

Nous devons modifier les vues existantes suivantes afin qu’elles incluent des groupes de contacts :

  • Views\Home\Create.aspx
  • Views\Home\Edit.aspx
  • Views\Home\Index.aspx

Vous pouvez voir les vues modifiées en examinant l’application Visual Studio qui accompagne ce didacticiel. Par exemple, la figure 8 illustre la vue Index de contact.

Vue Index de contact

Figure 08 : Affichage Index des contacts (cliquer pour afficher l’image en taille réelle)

Résumé

Dans cette itération, nous avons ajouté de nouvelles fonctionnalités à notre application Contact Manager en suivant une méthodologie de conception d’application de développement pilotée par les tests. Nous avons commencé par créer un ensemble de récits utilisateur. Nous avons créé un ensemble de tests unitaires qui correspond aux exigences exprimées par les récits utilisateur. Enfin, nous avons écrit juste assez de code pour répondre aux exigences exprimées par les tests unitaires.

Une fois que nous avons terminé d’écrire suffisamment de code pour répondre aux exigences exprimées par les tests unitaires, nous avons mis à jour notre base de données et nos vues. Nous avons ajouté une nouvelle table Groupes à notre base de données et mis à jour notre modèle de données Entity Framework. Nous avons également créé et modifié un ensemble de vues.

Dans l’itération suivante, l’itération finale, nous réécrirons notre application pour tirer parti d’Ajax. En tirant parti d’Ajax, nous améliorerons la réactivité et les performances de l’application Contact Manager.