Compartir a través de


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

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 ASP.NET MVC de administración de contactos (VB)

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

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

Lista 3: Models\Group.vb

Public Class Group

End Class

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

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

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

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

Lista 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

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

Lista 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

Lista 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 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 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

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

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

En realidad, no hemos implementado ninguno de los métodos relacionados con el trabajo con grupos de contactos en nuestra clase EntityContactManagerRepository real. 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 Function ListGroups() As IEnumerable(Of Group) Implements IContactManagerRepository.ListGroups

    throw New NotImplementedException()

End Function

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

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.