Compartir a través de


Iteración n.º 6: Usar el desarrollo mediante pruebas (C#)

por Microsoft

Descargar código

En esta sexta iteración, agregamos nueva funcionalidad a nuestra aplicación escribiendo primero pruebas unitarias y escribiendo código en las pruebas unitarias. En esta iteración, agregamos grupos de contactos.

Creación de una aplicación de administración de contactos ASP.NET MVC (C#)

En esta serie de tutoriales, creamos una aplicación completa de administración de contactos de principio a fin. La aplicación Contact Manager le permite almacenar información de contacto (nombres, números de teléfono y direcciones de correo electrónico) para obtener una lista de personas.

Compilamos la aplicación en varias iteraciones. Con cada iteración, mejoramos gradualmente la aplicación. El objetivo de este enfoque de iteración múltiple es permitirle comprender el motivo de cada cambio.

  • Iteración n.º 1: Crear la aplicación. En la primera iteración, creamos el Administrador de contactos de la manera más sencilla posible. Agregamos compatibilidad con operaciones básicas de base de datos: Crear, Leer, Actualizar y Eliminar (CRUD).

  • Iteración n.º 2: Hacer que la aplicación tenga un buen aspecto. En esta iteración, mejoraremos la apariencia de la aplicación modificando la página maestra de vistas y la hoja de estilos en cascada predeterminadas de ASP.NET MVC.

  • Iteración n.º 3: Agregar una validación de formulario. En la tercera iteración, agregamos validación básica de formularios. Evitamos que los usuarios envíen un formulario sin completar los campos de formulario obligatorios. También validamos las direcciones de correo electrónico y los números de teléfono.

  • Iteración n.º 4: Hacer que la aplicación tenga un acoplamiento flexible. En esta cuarta iteración, aprovechamos varios patrones de diseño de software para facilitar el mantenimiento y modificación de la aplicación Contact Manager. Por ejemplo, refactorizamos la aplicación para usar el patrón Repository y el patrón de inserción de dependencias.

  • Iteración n.º 5: Crear pruebas unitarias. En la quinta iteración, hacemos que la aplicación sea más fácil de mantener y modificar agregando pruebas unitarias. Simulamos nuestras clases de modelo de datos y compilamos pruebas unitarias para nuestros controladores y lógica de validación.

  • Iteración n.º 6: Usar el desarrollo mediante pruebas. En esta sexta iteración, agregamos nueva funcionalidad a nuestra aplicación escribiendo primero pruebas unitarias y escribiendo código en las pruebas unitarias. En esta iteración, agregamos grupos de contactos.

  • Iteración n.º 7: Agregar funcionalidad de Ajax. En la séptima iteración, mejoramos la capacidad de respuesta y el rendimiento de nuestra aplicación agregando compatibilidad con Ajax.

Esta iteración

En la iteración anterior de la aplicación Contact Manager, creamos pruebas unitarias para proporcionar una red de seguridad para nuestro código. La motivación para crear las pruebas unitarias era hacer que el código sea más resistente al cambio. Con las pruebas unitarias en vigor, podemos realizar cualquier cambio en nuestro código e inmediatamente saber si hemos roto la funcionalidad existente.

En esta iteración, usamos pruebas unitarias para un propósito completamente diferente. En esta iteración, usamos pruebas unitarias como parte de una filosofía de diseño de aplicaciones denominada desarrollo controlado por pruebas. Al practicar el desarrollo controlado por pruebas, primero escribe pruebas y, a continuación, escribe código en las pruebas.

Más concretamente, cuando se practica el desarrollo controlado por pruebas, hay tres pasos que se finalizan al crear el código (Rojo/Verde/Refactorización):

  1. Escribir una prueba unitaria que produce un error (Rojo)
  2. Escribir código que supere la prueba unitaria (Verde)
  3. Refactorizar el código (Refactorización)

En primer lugar, escriba la prueba unitaria. La prueba unitaria debe expresar su intención de cómo espera que se comporte el código. Cuando se crea por primera vez la prueba unitaria, se producirá un error en la prueba unitaria. La prueba debería fallar porque aún no ha escrito ningún código de aplicación que satisfaga la prueba.

A continuación, escribirá código suficiente para que se supere la prueba unitaria. El objetivo es escribir el código de la forma más perezosa, descuidada y rápida posible. No debe perder tiempo pensando en la arquitectura de la aplicación. En su lugar, debe centrarse en escribir la cantidad mínima de código necesaria para satisfacer la intención expresada por la prueba unitaria.

Por último, después de escribir código suficiente, puede retroceder y considerar la arquitectura general de la aplicación. En este paso, reescribirá (refactorizará) su código aprovechando los patrones de diseño de software (como el patrón de repositorio) para que su código sea más fácil de mantener. Puede volver a escribir el código sin miedo en este paso porque el código está cubierto por pruebas unitarias.

Son muchos los beneficios que se derivan de practicar el desarrollo controlado por pruebas. En primer lugar, el desarrollo controlado por pruebas obliga a centrarse en el código que realmente debe escribirse. Debido a que está constantemente enfocado en escribir solo la cantidad suficiente de código para pasar una prueba particular, esto le impide divagar y escribir grandes cantidades de código que nunca usará.

En segundo lugar, una metodología de diseño "probar primero" le obliga a escribir código desde la perspectiva de cómo se usará el código. En otras palabras, al practicar el desarrollo controlado por pruebas, está escribiendo constantemente las pruebas desde una perspectiva del usuario. Por lo tanto, el desarrollo controlado por pruebas puede dar lugar a API más limpias y comprensibles.

Por último, el desarrollo controlado por pruebas le obliga a escribir pruebas unitarias como parte del proceso normal de escritura de una aplicación. A medida que se acerca la fecha límite del proyecto, las pruebas suelen ser lo primero que se descarta. En cambio, si practica el desarrollo controlado por pruebas, es más probable que sea virtuoso escribiendo pruebas unitarias porque el desarrollo controlado por pruebas hace que las pruebas unitarias sean fundamentales en el proceso de desarrollar una aplicación.

Nota:

Para más información sobre el desarrollo controlado por pruebas, le recomiendo que lea el libro de Michael Feathers Working Effectively with Legacy Code (Trabajar de manera efectiva con código heredado).

En esta iteración, agregamos una nueva característica a nuestra aplicación Contact Manager. Agregamos compatibilidad con grupos de contactos. Puede usar los grupos de contactos para organizar sus contactos en categorías como grupos de Negocios y de Amigos.

Agregaremos esta nueva funcionalidad a nuestra aplicación siguiendo un proceso de desarrollo controlado por pruebas. Primero escribiremos nuestras pruebas unitarias y escribiremos todo nuestro código en estas pruebas.

Qué se prueba

Como hemos descrito en la iteración anterior, normalmente no escribe pruebas unitarias para la lógica de acceso a datos ni la lógica de vista. No escribe pruebas unitarias para la lógica de acceso a datos porque el acceso a una base de datos es una operación relativamente lenta. No escribe pruebas unitarias para la lógica de vista porque el acceso a una vista requiere poner en marcha un servidor web que es una operación relativamente lenta. No debe escribir una prueba unitaria a menos que la prueba se pueda ejecutar una y otra vez muy rápido.

Dado que el desarrollo controlado por pruebas unitarias está controlado por pruebas unitarias, nos centramos inicialmente en escribir controlador y lógica de negocios. Evitamos tocar la base de datos o las vistas. No modificaremos la base de datos ni crearemos nuestras vistas hasta el final de este tutorial. Empezamos con lo que se puede probar.

Creación de casos de usuario

Al practicar el desarrollo controlado por pruebas, siempre se empieza por escribir una prueba. Esto plantea inmediatamente la pregunta: ¿Cómo decide qué prueba escribir primero? Para responder a esta pregunta, debe escribir un conjunto de casos de usuario.

Un caso de usuario es una descripción muy breve (normalmente una frase) de un requisito de software. Debe ser una descripción no técnica de un requisito escrito desde la perspectiva del usuario.

Este es el conjunto de casos de usuario que describen las características necesarias para la nueva funcionalidad del grupo de contactos:

  1. El usuario puede ver una lista de grupos de contactos.
  2. El usuario puede crear un nuevo grupo de contactos.
  3. El usuario puede eliminar un grupo de contactos existente.
  4. El usuario puede seleccionar un grupo de contactos al crear un nuevo contacto.
  5. El usuario puede seleccionar un grupo de contactos al editar un contacto existente.
  6. En la vista Index se muestra una lista de grupos de contactos.
  7. Cuando un usuario hace clic en un grupo de contactos, se muestra una lista de contactos coincidentes.

Observe que esta lista de casos de usuario es totalmente comprensible para un cliente. No hay ninguna mención de los detalles técnicos de implementación.

Mientras desarrolla su aplicación, el conjunto de casos de usuario puede volverse más refinado. Puede dividir un caso de usuario en varios casos (requisitos). Por ejemplo, puede decidir que la creación de un nuevo grupo de contactos debe implicar la validación. Enviar un grupo de contactos sin un nombre debe devolver un error de validación.

Después de crear una lista de casos de usuario, estará listo para escribir su primera prueba unitaria. Comenzaremos creando una prueba unitaria para ver la lista de grupos de contactos.

Enumeración de grupos de contacto

Nuestro primer caso de usuario es que un usuario debe poder ver una lista de grupos de contactos. Necesitamos expresar este caso con una prueba.

Cree una nueva prueba unitaria haciendo clic con el botón derecho del ratón en la carpeta Controllers del proyecto ContactManager.Tests, seleccionando Agregar, Nueva prueba y seleccionando la plantilla Prueba unitaria (ver figura 1). Nombre la nueva prueba unitaria GroupControllerTest.cs y haga clic en el botón Aceptar.

Adding the GroupControllerTest unit test

Figura 01: Agregar la prueba unitaria GroupControllerTest (haga clic para ver la imagen de tamaño completo)

Nuestra primera prueba unitaria está incluida en la lista 1. Esta prueba verifica que el método Index() del controlador Group devuelve un conjunto de Grupos. La prueba comprueba que se devuelve una colección de grupos en los datos de vista.

Lista 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));
        }
    }
}

Cuando escriba por primera vez el código de la lista 1 en Visual Studio, obtendrá un montón de líneas rojas garabateadas. No hemos creado las clases GroupController o Group.

En este punto, ni siquiera podemos compilar nuestra aplicación, por lo que no podemos ejecutar nuestra primera prueba unitaria. Eso es bueno. Eso cuenta como una prueba con errores. Por lo tanto, ahora tenemos permiso para empezar a escribir código de aplicación. Necesitamos escribir código suficiente para ejecutar la prueba.

La clase del controlador Group en la lista 2 contiene el mínimo necesario de código para pasar la prueba unitaria. La acción Index() devuelve una lista codificada estáticamente de Grupos (la clase Group se define en la lista 3).

Lista 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);
        }

    }
}

Lista 3: Models\Group.cs

namespace ContactManager.Models
{
    public class Group
    {
    }
}

Después de agregar las clases GroupController y Group a nuestro proyecto, la primera prueba unitaria se completa correctamente (consulte la figura 2). Hemos realizado el trabajo mínimo necesario para superar la prueba. Es hora de celebrarlo.

Success!

Figura 02: ¡Conseguido! (haga clic para ver la imagen de tamaño completo)

Creación de grupos de contactos

Ahora podemos pasar al segundo caso de usuario. Es necesario poder crear nuevos grupos de contactos. Necesitamos expresar esta intención con una prueba.

La prueba de la lista 4 comprueba que al llamar al método Create() con un nuevo grupo se agrega el grupo a la lista de grupos devueltos por el método Index(). En otras palabras, si creo un nuevo grupo, debería poder volver a obtener el nuevo grupo de la lista de grupos devueltos por el método Index().

Lista 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);
}

La prueba de la lista 4 llama al método Create() del controlador Group con un nuevo grupo de contactos. A continuación, la prueba verifica que la llamada al método Index() del controlador Group devuelve el nuevo Grupo en los datos de la vista.

El controlador Group modificado de la lista 5 contiene el mínimo de cambios necesarios para pasar la nueva prueba.

Lista 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");
        }
    }
}

El controlador Group de la lista 5 tiene una nueva acción Create(). Esta acción agrega un grupo a una colección de grupos. Observe que la acción Index() se ha modificado para devolver el contenido de la colección de grupos.

Una vez más, hemos realizado la cantidad mínima de trabajo necesaria para superar la prueba unitaria. Después de realizar estos cambios en el controlador Group, todas nuestras pruebas unitarias pasan.

Adición de validación

Este requisito no se indicó explícitamente en el caso del usuario. Sin embargo, es razonable exigir que un grupo tenga un nombre. De lo contrario, la organización de contactos en grupos no sería muy útil.

La lista 6 contiene una nueva prueba que expresa esta intención. Esta prueba comprueba que el intento de crear un grupo sin proporcionar un nombre da como resultado un mensaje de error de validación en el estado del modelo.

Lista 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);
}

Para satisfacer esta prueba, es necesario agregar una propiedad Name a nuestra clase Group (vea Lista 7). Además, tenemos que agregar un poco de lógica de validación a la acción Create() de nuestro controlador Group (ver lista 8).

Lista 7: Models\Group.cs

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

Lista 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");
}

Observe que la acción Create() del controlador Group contiene ahora lógica de validación y de base de datos. Actualmente, la base de datos usada por el controlador Group consiste en nada más que una colección en memoria.

Hora de refactorizar

El tercer paso de Rojo/Verde/Refactorización es la parte de Refactorización. Llegados a este punto, necesitamos volver atrás en nuestro código y considerar cómo podemos refactorizar nuestra aplicación para mejorar su diseño. La fase de refactorización es la fase en la que pensamos detenidamente en la mejor manera de implementar los principios y patrones de diseño de software.

Somos libres de modificar nuestro código de cualquier forma que elijamos para mejorar su diseño. Tenemos una red de seguridad de pruebas unitarias que nos impiden romper la funcionalidad existente.

Ahora mismo, nuestro controlador Group es un desastre desde la perspectiva de un buen diseño de software. El controlador Group contiene una maraña de código de validación y acceso a los datos. Para evitar infringir el Principio de responsabilidad única, necesitamos separar estos problemas en diferentes clases.

Nuestra clase de controlador Group refactorizada figura en la lista 9. El controlador se ha modificado para usar el nivel de servicio ContactManager. Este es el mismo nivel de servicio que usamos con el controlador Contact.

La lista 10 contiene los nuevos métodos agregados al nivel de servicio ContactManager para admitir la validación, la descripción y la creación de grupos. La interfaz IContactManagerService se actualizó para incluir los nuevos métodos.

La lista 11 contiene una nueva clase FakeContactManagerRepository que implementa la interfaz IContactManagerRepository. A diferencia de la clase EntityContactManagerRepository que también implementa la interfaz IContactManagerRepository, nuestra nueva clase FakeContactManagerRepository no se comunica con la base de datos. La clase FakeContactManagerRepository usa una colección en memoria como proxy para la base de datos. Usaremos esta clase en nuestras pruebas unitarias como una capa de repositorio falsa.

Lista 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");
        }
    }
}

Lista 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();
}

Lista 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 modificación de la interfaz IContactManagerRepository requiere que implementemos los métodos CreateGroup() y ListGroups() en la clase EntityContactManagerRepository. La forma más perezosa y rápida de hacerlo es agregar métodos stub que tengan este aspecto:

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

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

Por último, estos cambios en el diseño de nuestra aplicación requieren que realicemos algunas modificaciones en nuestras pruebas unitarias. Ahora es necesario usar FakeContactManagerRepository al realizar las pruebas unitarias. La clase GroupControllerTest actualizada se encuentra en la lista 12.

Lista 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);
        }
    
    }
}

Después de realizar todos estos cambios, una vez más, todas nuestras pruebas unitarias pasan. Hemos completado todo el ciclo de Rojo/Verde/Refactorización. Hemos implementado los dos primeros casos de usuario. Ahora tenemos pruebas unitarias de apoyo para los requisitos expresados en los casos de usuario. La implementación del resto de los casos de usuario implica repetir el mismo ciclo de Rojo/Verde/Refactorización.

Modificación de nuestra base de datos

Por desgracia, aunque hayamos satisfecho todos los requisitos expresados por nuestras pruebas unitarias, nuestro trabajo no ha terminado. Todavía necesitamos modificar nuestra base de datos.

Es necesario crear una nueva tabla de base de datos de grupo. Siga estos pasos:

  1. En la ventana Explorador de servidores, haga clic con el botón derecho en la carpeta Tables y seleccione la opción de menú Agregar nueva tabla.
  2. Escriba las dos columnas que se describen a continuación en el Diseñador de tablas.
  3. Marque la columna Id como una clave principal y la columna Identidad.
  4. Guarde la nueva tabla con el nombre Grupos haciendo clic en el icono del disquete.

Nombre de la columna Tipo de datos Permitir valores NULL
Identificador int False
Nombre nvarchar(50) False

A continuación, debemos eliminar todos los datos de la tabla Contactos (de lo contrario, no podremos crear una relación entre las tablas Contactos y Grupos). Siga estos pasos:

  1. Haga clic con el botón derecho en la tabla Contactos y seleccione la opción de menú Mostrar datos de tabla.
  2. Elimine todas las filas.

A continuación, es necesario definir una relación entre la tabla de base de datos Grupos y la tabla de base de datos Contactos existente. Siga estos pasos:

  1. Haga doble clic en la tabla Contactos de la ventana Explorador de servidores para abrir el Diseñador de tablas.
  2. Agregue una nueva columna de tipo entero a la tabla Contactos llamada GroupId.
  3. Haga clic en el botón Relación para abrir el cuadro de diálogo Relaciones de clave externa (vea la figura 3).
  4. Haga clic en el botón Agregar.
  5. Haga clic en el botón de puntos suspensivos que aparece junto al botón Especificación de tabla y columnas.
  6. En el cuadro de diálogo Tablas y columnas, seleccione Grupos como la tabla de claves principal e Id como columna de clave principal. Seleccione Contactos como la tabla de claves externas y GroupId como columna de clave externa (vea la figura 4). Haga clic en el botón Aceptar .
  7. En Especificación de INSERT y UPDATE, seleccione el valor Cascada para Regla de eliminación.
  8. Haga clic en el botón Cerrar para cerrar el cuadro de diálogo Relaciones de clave externa.
  9. Haga clic en el botón Guardar para guardar los cambios en la tabla Contactos.

Creating a database table relationship

Figura 03: Creación de una relación de tablas de base de datos (haga clic para ver la imagen de tamaño completo)

Specifying table relationships

Figura 04: Especificación de las relaciones entre tablas (haga clic para ver la imagen de tamaño completo)

Actualización del modelo de datos

A continuación, es necesario actualizar el modelo de datos para representar la nueva tabla de base de datos. Siga estos pasos:

  1. Haga doble clic en el archivo ContactManagerModel.edmx de la carpeta Models para abrir el Diseñador de entidades.
  2. Haga clic con el botón derecho en la superficie del diseñador y seleccione la opción de menú Actualizar modelo desde base de datos.
  3. En el Asistente para actualización, seleccione la tabla Grupos y haga clic en el botón Finalizar (vea la figura 5).
  4. Haga clic con el botón derecho en la entidad Groups y seleccione la opción de menú Cambiar nombre. Cambie el nombre de la entidad Groups a Group. (singular).
  5. Haga clic con el botón derecho del ratón en la propiedad de navegación Groups que aparece en la parte inferior de la entidad Contact. Cambie el nombre de la propiedad de navegación Groups por Group. (singular).

Updating an Entity Framework model from the database

Figura 05: Actualización de un modelo de Entity Framework desde la base de datos (haga clic para ver la imagen de tamaño completo)

Después de completar estos pasos, el modelo de datos representará las tablas Contactos y Grupos. El Diseñador de entidades debe mostrar ambas entidades (vea la figura 6).

Entity Designer displaying Group and Contact

Figura 06: Diseñador de entidades que muestra Grupo y Contacto (haga clic para ver la imagen de tamaño completo)

Creación de nuestras clases de repositorio

A continuación, es necesario implementar nuestra clase de repositorio. A lo largo de esta iteración, agregamos varios métodos nuevos a la interfaz IContactManagerRepository al escribir código para satisfacer nuestras pruebas unitarias. La versión final de la interfaz IContactManagerRepository se encuentra en la lista 14.

Lista 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);
    }
}

En realidad, no hemos implementado ninguno de los métodos relacionados con el trabajo con grupos de contactos. Actualmente, la clase EntityContactManagerRepository tiene métodos de código auxiliar para cada uno de los métodos del grupo de contactos enumerados en la interfaz IContactManagerRepository. Por ejemplo, el método ListGroups() tiene el siguiente aspecto:

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

Los métodos de código auxiliar nos permiten compilar nuestra aplicación y pasar las pruebas unitarias. Sin embargo, ahora es el momento de implementar realmente estos métodos. La versión final de la clase EntityContactManagerRepository se encuentra en la lista 13.

Lista 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();

        }

    }
}

Creación de las vistas

La aplicación ASP.NET MVC cuando usa el motor de vista ASP.NET predeterminado. Por lo tanto, no cree vistas en respuesta a una prueba unitaria determinada. Sin embargo, dado que una aplicación no sería útil sin vistas, no podemos completar esta iteración sin crear ni modificar las vistas contenidas en la aplicación Contact Manager.

Es necesario crear las siguientes vistas nuevas para administrar grupos de contactos (vea la figura 7):

  • Views\Group\Index.aspx: muestra la lista de grupos de contactos
  • Views\Group\Delete.aspx: muestra el formulario de confirmación para eliminar un grupo de contactos

The Group Index view

Figura 07: Vista Índice de grupos (haga clic para ver la imagen de tamaño completo)

Es necesario modificar las siguientes vistas existentes para que incluyan grupos de contactos:

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

Puede ver las vistas modificadas examinando la aplicación de Visual Studio que acompaña a este tutorial. Por ejemplo, la figura 8 muestra la vista Índice de contactos.

The Contact Index view

Figura 08: Vista Índice de contactos (haga clic para ver la imagen de tamaño completo)

Resumen

En esta iteración, hemos agregado nuevas funcionalidades a nuestra aplicación Contact Manager siguiendo una metodología de diseño de aplicaciones de desarrollo basado en pruebas. Empezamos creando un conjunto de casos de usuario. Hemos creado un conjunto de pruebas unitarias que se corresponden con los requisitos expresados por los casos de usuario. Por último, hemos escrito código suficiente para satisfacer los requisitos expresados por las pruebas unitarias.

Después de terminar de escribir código suficiente para satisfacer los requisitos expresados por las pruebas unitarias, actualizamos nuestra base de datos y vistas. Hemos agregado una nueva tabla Grupos a nuestra base de datos y hemos actualizado el modelo de datos de Entity Framework. También hemos creado y modificado un conjunto de vistas.

En la siguiente iteración (la iteración final) reescribimos nuestra aplicación para aprovechar las ventajas de Ajax. Al aprovechar Ajax, mejoraremos la capacidad de respuesta y el rendimiento de la aplicación Contact Manager.