Partager via


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

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 (C#)

Dans cette série de tutoriels, nous créons une application gestion des contacts entière 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 d’itération multiple est de vous permettre de comprendre la raison de chaque modification.

  • Itération n°1 - Créer 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 : Créer, Lire, Mettre à jour et Supprimer (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 #3 - Ajouter la validation de formulaire. Dans la troisième itération, nous ajoutons la validation de formulaire de base. Nous empêchant les utilisateurs d’envoyer un formulaire sans remplir les champs de formulaire obligatoires. 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 nous appuyons sur plusieurs modèles de conception de logiciels 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 n°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 nous moquons de nos classes de modèle de données et nous 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 la 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 entièrement 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, lorsque vous pratiquez le développement piloté par les tests, vous effectuez 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 (Refactorisation)

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 au 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 paresseux, la plus simple 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 envisager l’architecture globale de votre application. Dans cette étape, vous réécrire (refactoriser) 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 à cette étape, car votre code est couvert par des tests unitaires.

De nombreux avantages découlent de la pratique du développement piloté par les tests. Tout d’abord, le développement piloté par les tests vous oblige à vous concentrer sur le code qui doit réellement être écrit. Étant donné que vous êtes constamment concentré sur l’écriture de suffisamment de code pour réussir un test particulier, vous ne pouvez pas vous promener 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, le test est 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 quant à l’écriture de tests unitaires, car le développement piloté par les tests rend les tests unitaires centraux dans le 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’entreprise et d’amis.

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é encore et encore 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 d’un contrôleur et d’une logique métier. Nous évitons 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 pose immédiatement la question suivante : comment décidez-vous du test à écrire en premier ? Pour répondre à cette question, vous devez écrire un ensemble de témoignages utilisateur.

Un récit utilisateur est une description très brève (généralement 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 témoignages 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. La liste des groupes de contacts s’affiche dans la vue Index.
  7. Lorsqu’un utilisateur clique sur un groupe de contacts, la liste des contacts correspondants s’affiche.

Notez que cette liste de témoignages d’utilisateurs est parfaitement compréhensible par un client. Il n’existe aucune mention de détails d’implémentation technique.

Lors du processus de création de votre application, l’ensemble de 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 nouveau groupe de contacts doit impliquer la validation. L’envoi d’un groupe de contacts sans nom doit renvoyer une erreur de validation.

Après avoir créé une liste de témoignages 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.cs, puis cliquez sur le bouton OK .

Ajout du test unitaire GroupControllerTest

Figure 01 : Ajout du test unitaire GroupControllerTest (Cliquez 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 d’affichage.

Listing 1 - Controllers\GroupControllerTest.cs

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Tests.Controllers
{
    [TestClass]
    public class GroupControllerTest
    {

        [TestMethod]
        public void Index()
        {
            // Arrange
            var controller = new GroupController();

            // Act
            var result = (ViewResult)controller.Index();
        
            // Assert
            Assert.IsInstanceOfType(result.ViewData.Model, typeof(IEnumerable));
        }
    }
}

Lorsque vous tapez pour la première fois le code 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 générer notre application afin de ne pas exécuter notre premier test unitaire. C’est bien. Cela compte comme un test d’échec. 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 de 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.cs

using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Controllers
{
    public class GroupController : Controller
    {
        public ActionResult Index()
        {
            var groups = new List();
            return View(groups);
        }

    }
}

Listing 3 - Models\Group.cs

namespace ContactManager.Models
{
    public class Group
    {
    }
}

Une fois que nous avons ajouté les classes GroupController et Group à notre projet, notre premier test unitaire s’est terminé avec succès (voir figure 2). Nous avons effectué le travail minimal requis 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 être en mesure de créer de nouveaux groupes de contacts. Nous devons exprimer cette intention avec 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 renvoyée par la méthode Index(). En d’autres termes, si je crée un groupe, je dois pouvoir récupérer le nouveau groupe à partir de la liste de groupes retournée par la méthode Index().

Listing 4 - Controllers\GroupControllerTest.cs

[TestMethod]
public void Create()
{
    // Arrange
    var controller = new GroupController();

    // Act
    var groupToCreate = new Group();
    controller.Create(groupToCreate);

    // Assert
    var result = (ViewResult)controller.Index();
    var groups = (IEnumerable<Group>)result.ViewData.Model;
    CollectionAssert.Contains(groups.ToList(), groupToCreate);
}

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 d’affichage.

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

using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Models;
using System.Collections;

namespace ContactManager.Controllers
{
    public class GroupController : Controller
    {
        private IList<Group> _groups = new List<Group>();

        public ActionResult Index()
        {
            return View(_groups);
        }

        public ActionResult Create(Group groupToCreate)
        {
            _groups.Add(groupToCreate);
            return RedirectToAction("Index");
        }
    }
}

Le contrôleur de groupe de 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 renvoyer le contenu de la collection de Groupes.

Une fois de plus, nous avons effectué le 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 indiqué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 à l’état du modèle.

Listing 6 - Controllers\GroupControllerTest.cs

[TestMethod]
public void CreateRequiredName()
{
    // Arrange
    var controller = new GroupController();

    // Act
    var groupToCreate = new Group();
    groupToCreate.Name = String.Empty;
    var result = (ViewResult)controller.Create(groupToCreate);

    // Assert
    var error = result.ViewData.ModelState["Name"].Errors[0];
    Assert.AreEqual("Name is required.", error.ErrorMessage);
}

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

Listing 7 - Models\Group.cs

namespace ContactManager.Models
{
    public class Group
    {
        public string Name { get; set; }
    }
}

Listing 8 - Controllers\GroupController.cs

public ActionResult Create(Group groupToCreate)
{
    // Validation logic
    if (groupToCreate.Name.Trim().Length == 0)
    {
        ModelState.AddModelError("Name", "Name is required.");
        return View("Create");
    }
    
    // Database logic
    _groups.Add(groupToCreate);
    return RedirectToAction("Index");
}

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

Délai de refactorisation

La troisième étape en rouge/vert/refactorisation est la partie Refactorisation. À ce stade, nous devons prendre du recul par-dessus 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 les principes et modèles de conception de logiciels.

Nous sommes libres de modifier notre code de n’importe quelle 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 briser 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 enchevêtrement de code de validation et d’accès aux données. 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ée 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 contacts.

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.

Liste 9 - Controllers\GroupController.cs

using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Controllers
{
    public class GroupController : Controller
    {

        private IContactManagerService _service;

        public GroupController()
        {
            _service = new ContactManagerService(new ModelStateWrapper(this.ModelState));
        }

        public GroupController(IContactManagerService service)
        {
            _service = service;
        }

        public ActionResult Index()
        {
            return View(_service.ListGroups());
        }

        public ActionResult Create(Group groupToCreate)
        {
            if (_service.CreateGroup(groupToCreate))
                return RedirectToAction("Index");
            return View("Create");
        }
    }
}

Listing 10 - Controllers\ContactManagerService.cs

public bool ValidateGroup(Group groupToValidate)
{
    if (groupToValidate.Name.Trim().Length == 0)
       _validationDictionary.AddError("Name", "Name is required.");
    return _validationDictionary.IsValid;
}

public bool CreateGroup(Group groupToCreate)
{
    // Validation logic
    if (!ValidateGroup(groupToCreate))
        return false;

    // Database logic
    try
    {
        _repository.CreateGroup(groupToCreate);
    }
    catch
    {
        return false;
    }
    return true;
}

public IEnumerable<Group> ListGroups()
{
    return _repository.ListGroups();
}

Listing 11 - Controllers\FakeContactManagerRepository.cs

using System;
using System.Collections.Generic;
using ContactManager.Models;

namespace ContactManager.Tests.Models
{
    public class FakeContactManagerRepository : IContactManagerRepository
    {
        private IList<Group> _groups = new List<Group>(); 
        
        #region IContactManagerRepository Members

        // Group methods

        public Group CreateGroup(Group groupToCreate)
        {
            _groups.Add(groupToCreate);
            return groupToCreate;
        }

        public IEnumerable<Group> ListGroups()
        {
            return _groups;
        }

        // Contact methods
        
        public Contact CreateContact(Contact contactToCreate)
        {
            throw new NotImplementedException();
        }

        public void DeleteContact(Contact contactToDelete)
        {
            throw new NotImplementedException();
        }

        public Contact EditContact(Contact contactToEdit)
        {
            throw new NotImplementedException();
        }

        public Contact GetContact(int id)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<Contact> ListContacts()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

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’effectuer cette opération consiste à ajouter des méthodes stub qui ressemblent à ceci :

public Group CreateGroup(Group groupToCreate)
{
    throw new NotImplementedException();
}

public IEnumerable<Group> ListGroups()
{
    throw new NotImplementedException();
}

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

Listing 12 - Controllers\GroupControllerTest.cs

using System.Collections.Generic;
using System.Web.Mvc;
using ContactManager.Controllers;
using ContactManager.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections;
using System.Linq;
using System;
using ContactManager.Tests.Models;

namespace ContactManager.Tests.Controllers
{
    [TestClass]
    public class GroupControllerTest
    {
        private IContactManagerRepository _repository;
        private ModelStateDictionary _modelState;
        private IContactManagerService _service;

        [TestInitialize]
        public void Initialize()
        {
            _repository = new FakeContactManagerRepository();
            _modelState = new ModelStateDictionary();
            _service = new ContactManagerService(new ModelStateWrapper(_modelState), _repository);

        }

        [TestMethod]
        public void Index()
        {
            // Arrange
            var controller = new GroupController(_service);

            // Act
            var result = (ViewResult)controller.Index();
        
            // Assert
            Assert.IsInstanceOfType(result.ViewData.Model, typeof(IEnumerable));
        }

        [TestMethod]
        public void Create()
        {
            // Arrange
            var controller = new GroupController(_service);

            // Act
            var groupToCreate = new Group();
            groupToCreate.Name = "Business";
            controller.Create(groupToCreate);

            // Assert
            var result = (ViewResult)controller.Index();
            var groups = (IEnumerable)result.ViewData.Model;
            CollectionAssert.Contains(groups.ToList(), groupToCreate);
        }

        [TestMethod]
        public void CreateRequiredName()
        {
            // Arrange
            var controller = new GroupController(_service);

            // Act
            var groupToCreate = new Group();
            groupToCreate.Name = String.Empty;
            var result = (ViewResult)controller.Create(groupToCreate);

            // Assert
            var error = _modelState["Name"].Errors[0];
            Assert.AreEqual("Name is required.", error.ErrorMessage);
        }
    
    }
}

Une fois que nous avons apporté toutes ces modifications, toutes nos tests unitaires réussissent. Nous avons terminé l’ensemble 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 la répétition du 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 terminé. 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 comme clé primaire et la 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.cs

using System.Collections.Generic;

namespace ContactManager.Models
{
    public interface IContactManagerRepository
    {
        // Contact methods
        Contact CreateContact(int groupId, Contact contactToCreate);
        void DeleteContact(Contact contactToDelete);
        Contact EditContact(int groupId, Contact contactToEdit);
        Contact GetContact(int id);

        // Group methods
        Group CreateGroup(Group groupToCreate);
        IEnumerable<Group> ListGroups();
        Group GetGroup(int groupId);
        Group GetFirstGroup();
        void DeleteGroup(Group groupToDelete);
    }
}

Nous n’avons implémenté aucune des méthodes liées à l’utilisation des groupes de contacts. 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 IEnumerable<Group> ListGroups()
{
    throw new NotImplementedException();
}

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

using System.Collections.Generic;
using System.Linq;
using System;

namespace ContactManager.Models
{
    public class EntityContactManagerRepository : ContactManager.Models.IContactManagerRepository
    {
        private ContactManagerDBEntities _entities = new ContactManagerDBEntities();

        // Contact methods

        public Contact GetContact(int id)
        {
            return (from c in _entities.ContactSet.Include("Group")
                    where c.Id == id
                    select c).FirstOrDefault();
        }

        public Contact CreateContact(int groupId, Contact contactToCreate)
        {
            // Associate group with contact
            contactToCreate.Group = GetGroup(groupId);

            // Save new contact
            _entities.AddToContactSet(contactToCreate);
            _entities.SaveChanges();
            return contactToCreate;
        }

        public Contact EditContact(int groupId, Contact contactToEdit)
        {
            // Get original contact
            var originalContact = GetContact(contactToEdit.Id);
            
            // Update with new group
            originalContact.Group = GetGroup(groupId);
            
            // Save changes
            _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);
            _entities.SaveChanges();
            return contactToEdit;
        }

        public void DeleteContact(Contact contactToDelete)
        {
            var originalContact = GetContact(contactToDelete.Id);
            _entities.DeleteObject(originalContact);
            _entities.SaveChanges();
        }

        public Group CreateGroup(Group groupToCreate)
        {
            _entities.AddToGroupSet(groupToCreate);
            _entities.SaveChanges();
            return groupToCreate;
        }

        // Group Methods

        public IEnumerable<Group> ListGroups()
        {
            return _entities.GroupSet.ToList();
        }

        public Group GetFirstGroup()
        {
            return _entities.GroupSet.Include("Contacts").FirstOrDefault();
        }

        public Group GetGroup(int id)
        {
            return (from g in _entities.GroupSet.Include("Contacts")
                       where g.Id == id
                       select g).FirstOrDefault();
        }

        public void DeleteGroup(Group groupToDelete)
        {
            var originalGroup = GetGroup(groupToDelete.Id);
            _entities.DeleteObject(originalGroup);
            _entities.SaveChanges();

        }

    }
}

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.