Itération #6 : Utiliser le développement piloté par les tests (VB)
par Microsoft
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) :
- Écrire un test unitaire qui échoue (Rouge)
- Écrire du code qui réussit le test unitaire (vert)
- 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 :
- L’utilisateur peut afficher une liste de groupes de contacts.
- L’utilisateur peut créer un groupe de contacts.
- L’utilisateur peut supprimer un groupe de contacts existant.
- L’utilisateur peut sélectionner un groupe de contacts lors de la création d’un contact.
- L’utilisateur peut sélectionner un groupe de contacts lors de la modification d’un contact existant.
- Une liste de groupes de contacts s’affiche dans la vue Index.
- 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 .
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.
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 :
- 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.
- Entrez les deux colonnes décrites ci-dessous dans la table Designer.
- Marquez la colonne Id en tant que clé primaire et colonne Identité.
- 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 :
- Cliquez avec le bouton droit sur la table Contacts et sélectionnez l’option de menu Afficher les données du tableau.
- 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 :
- Double-cliquez sur la table Contacts dans la fenêtre Server Explorer pour ouvrir le Designer table.
- Ajoutez une nouvelle colonne entière à la table Contacts nommée GroupId.
- Cliquez sur le bouton Relation pour ouvrir la boîte de dialogue Relations de clé étrangère (voir figure 3).
- Cliquez sur le bouton Ajouter.
- Cliquez sur le bouton de sélection qui s’affiche en regard du bouton Spécification de la table et des colonnes.
- 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.
- Sous INSERT et UPDATE Specification, sélectionnez la valeur Cascade pour Supprimer la règle.
- Cliquez sur le bouton Fermer pour fermer la boîte de dialogue Relations de clé étrangère.
- Cliquez sur le bouton Enregistrer pour enregistrer les modifications apportées à la table Contacts.
Figure 03 : Création d’une relation de table de base de données (cliquer pour afficher l’image de taille réelle)
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 :
- Double-cliquez sur le fichier ContactManagerModel.edmx dans le dossier Models pour ouvrir le Designer d’entité.
- 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.
- Dans l’Assistant Mise à jour, sélectionnez la table Groupes, puis cliquez sur le bouton Terminer (voir figure 5).
- 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).
- 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).
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).
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
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.
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.