Compartilhar via


Iteração nº 6 – usar desenvolvimento controlado por testes (VB)

pela Microsoft

Baixar código

Nesta sexta iteração, adicionamos uma nova funcionalidade ao nosso aplicativo escrevendo testes de unidade primeiro e escrevendo código nos testes de unidade. Nesta iteração, adicionamos grupos de contatos.

Criando um VB (aplicativo MVC) ASP.NET gerenciamento de contatos

Nesta série de tutoriais, criamos um aplicativo de Gerenciamento de Contatos inteiro do início ao fim. O aplicativo Gerenciador de Contatos permite que você armazene informações de contato - nomes, números de telefone e endereços de email - para uma lista de pessoas.

Criamos o aplicativo em várias iterações. A cada iteração, aprimoramos gradualmente o aplicativo. O objetivo dessa abordagem de iteração múltipla é permitir que você entenda o motivo de cada alteração.

  • Iteração nº 1 – Criar o aplicativo. Na primeira iteração, criamos o Gerenciador de Contatos da maneira mais simples possível. Adicionamos suporte para operações básicas de banco de dados: CRUD (Criar, Ler, Atualizar e Excluir).

  • Iteração nº 2 – Deixe o aplicativo bonito. Nesta iteração, melhoramos a aparência do aplicativo modificando o modo de exibição padrão ASP.NET MVC master página e a folha de estilos em cascata.

  • Iteração nº 3 – Adicionar validação de formulário. Na terceira iteração, adicionamos validação de formulário básico. Impedimos que as pessoas enviem um formulário sem concluir os campos de formulário necessários. Também validamos endereços de email e números de telefone.

  • Iteração nº 4 – acoplar o aplicativo livremente. Nesta quarta iteração, aproveitamos vários padrões de design de software para facilitar a manutenção e a modificação do aplicativo Contact Manager. Por exemplo, refatoramos nosso aplicativo para usar o padrão de repositório e o padrão de injeção de dependência.

  • Iteração nº 5 – Criar testes de unidade. Na quinta iteração, facilitamos a manutenção e a modificação do aplicativo adicionando testes de unidade. Simulamos nossas classes de modelo de dados e criamos testes de unidade para nossos controladores e lógica de validação.

  • Iteração nº 6 – Usar o desenvolvimento controlado por teste. Nesta sexta iteração, adicionamos uma nova funcionalidade ao nosso aplicativo escrevendo testes de unidade primeiro e escrevendo código nos testes de unidade. Nesta iteração, adicionamos grupos de contatos.

  • Iteração nº 7 – Adicionar funcionalidade do Ajax. Na sétima iteração, melhoramos a capacidade de resposta e o desempenho do nosso aplicativo adicionando suporte para o Ajax.

Esta Iteração

Na iteração anterior do aplicativo Contact Manager, criamos testes de unidade para fornecer uma rede de segurança para nosso código. A motivação para criar os testes de unidade foi tornar nosso código mais resiliente a alterações. Com os testes de unidade em vigor, podemos fazer qualquer alteração em nosso código e saber imediatamente se quebramos a funcionalidade existente.

Nesta iteração, usamos testes de unidade para uma finalidade totalmente diferente. Nesta iteração, usamos testes de unidade como parte de uma filosofia de design de aplicativo chamada desenvolvimento controlado por teste. Ao praticar o desenvolvimento orientado a testes, você escreve testes primeiro e, em seguida, escreve código nos testes.

Mais precisamente, ao praticar o desenvolvimento orientado a testes, há três etapas que você conclui ao criar código (Vermelho/Verde/Refatorar):

  1. Gravar um teste de unidade que falha (vermelho)
  2. Escrever código que passa no teste de unidade (Verde)
  3. Refatorar seu código (Refatorar)

Primeiro, você escreve o teste de unidade. O teste de unidade deve expressar sua intenção de como você espera que seu código se comporte. Quando você cria o teste de unidade pela primeira vez, o teste de unidade deve falhar. O teste deve falhar porque você ainda não escreveu nenhum código de aplicativo que satisfaça o teste.

Em seguida, você escreve apenas código suficiente para que o teste de unidade seja aprovado. O objetivo é escrever o código da maneira mais preguiçosa, desleixada e mais rápida possível. Você não deve perder tempo pensando na arquitetura do seu aplicativo. Em vez disso, você deve se concentrar em escrever a quantidade mínima de código necessária para atender à intenção expressa pelo teste de unidade.

Por fim, depois de escrever código suficiente, você pode voltar atrás e considerar a arquitetura geral do seu aplicativo. Nesta etapa, você reescreverá (refatorar) seu código aproveitando os padrões de design de software, como o padrão de repositório, para que seu código seja mais mantenedível. Você pode reescrever o código sem medo nesta etapa porque seu código é coberto por testes de unidade.

Há muitos benefícios resultantes da prática do desenvolvimento orientado a testes. Primeiro, o desenvolvimento controlado por testes força você a se concentrar no código que realmente precisa ser escrito. Como você está constantemente focado em apenas escrever código suficiente para passar em um teste específico, você é impedido de vagar pelas identificações e escrever grandes quantidades de código que você nunca usará.

Em segundo lugar, uma metodologia de design "testar primeiro" força você a escrever código da perspectiva de como seu código será usado. Em outras palavras, ao praticar o desenvolvimento orientado a testes, você está constantemente escrevendo seus testes de uma perspectiva do usuário. Portanto, o desenvolvimento controlado por testes pode resultar em APIs mais limpas e compreensíveis.

Por fim, o desenvolvimento controlado por testes força você a escrever testes de unidade como parte do processo normal de gravação de um aplicativo. À medida que um prazo do projeto se aproxima, o teste normalmente é a primeira coisa que sai pela janela. Ao praticar o desenvolvimento orientado a testes, por outro lado, é mais provável que você seja virtuoso em escrever testes de unidade porque o desenvolvimento controlado por testes torna os testes de unidade centrais para o processo de criação de um aplicativo.

Observação

Para saber mais sobre o desenvolvimento orientado a testes, recomendo que você leia o livro de Michael Feathers Trabalhando efetivamente com código herdado.

Nesta iteração, adicionamos um novo recurso ao nosso aplicativo Do Gerenciador de Contatos. Adicionamos suporte para Grupos de Contatos. Você pode usar Grupos de Contatos para organizar seus contatos em categorias como grupos de Negócios e Amigos.

Adicionaremos essa nova funcionalidade ao nosso aplicativo seguindo um processo de desenvolvimento controlado por testes. Escreveremos nossos testes de unidade primeiro e escreveremos todo o código nesses testes.

O que é testado

Como discutimos na iteração anterior, você normalmente não grava testes de unidade para lógica de acesso a dados ou lógica de exibição. Você não escreve testes de unidade para lógica de acesso a dados porque acessar um banco de dados é uma operação relativamente lenta. Você não escreve testes de unidade para a lógica de exibição porque acessar uma exibição requer girar um servidor Web que é uma operação relativamente lenta. Você não deve escrever um teste de unidade, a menos que o teste possa ser executado repetidamente muito rapidamente

Como o desenvolvimento orientado a testes é orientado por testes de unidade, nos concentramos inicialmente na escrita do controlador e da lógica de negócios. Evitamos tocar no banco de dados ou nas exibições. Não modificaremos o banco de dados nem criaremos nossas exibições até o final deste tutorial. Começamos com o que pode ser testado.

Criando histórias de usuário

Ao praticar o desenvolvimento orientado a testes, você sempre começa escrevendo um teste. Isso imediatamente levanta a questão: Como você decide qual teste escrever primeiro? Para responder a essa pergunta, você deve escrever um conjunto de histórias de usuários.

Uma história de usuário é uma descrição muito breve (geralmente uma frase) de um requisito de software. Deve ser uma descrição não técnica de um requisito escrito de uma perspectiva do usuário.

Aqui está o conjunto de histórias de usuários que descrevem os recursos exigidos pela nova funcionalidade do Grupo de Contatos:

  1. O usuário pode exibir uma lista de grupos de contatos.
  2. O usuário pode criar um novo grupo de contatos.
  3. O usuário pode excluir um grupo de contatos existente.
  4. O usuário pode selecionar um grupo de contatos ao criar um novo contato.
  5. O usuário pode selecionar um grupo de contatos ao editar um contato existente.
  6. Uma lista de grupos de contatos é exibida na exibição Índice.
  7. Quando um usuário clica em um grupo de contatos, uma lista de contatos correspondentes é exibida.

Observe que essa lista de histórias de usuários é completamente compreensível por um cliente. Não há menção de detalhes de implementação técnica.

Durante o processo de criação do aplicativo, o conjunto de histórias de usuários pode se tornar mais refinado. Você pode dividir uma história de usuário em várias histórias (requisitos). Por exemplo, você pode decidir que a criação de um novo grupo de contatos deve envolver a validação. Enviar um grupo de contatos sem um nome deve retornar um erro de validação.

Depois de criar uma lista de histórias de usuários, você estará pronto para escrever seu primeiro teste de unidade. Começaremos criando um teste de unidade para exibir a lista de grupos de contatos.

Listando grupos de contatos

Nossa primeira história de usuário é que um usuário deve ser capaz de exibir uma lista de grupos de contatos. Precisamos expressar essa história com um teste.

Crie um novo teste de unidade clicando com o botão direito do mouse na pasta Controladores no projeto ContactManager.Tests, selecionando Adicionar, Novo Teste e selecionando o modelo Teste de Unidade (consulte a Figura 1). Nomeie o novo teste de unidade GroupControllerTest.vb e clique no botão OK .

Adicionando o teste de unidade GroupControllerTest

Figura 01: Adicionando o teste de unidade GroupControllerTest (clique para exibir a imagem em tamanho real)

Nosso primeiro teste de unidade está contido na Listagem 1. Esse teste verifica se o método Index() do controlador Group retorna um conjunto de Grupos. O teste verifica se uma coleção de Grupos é retornada na exibição de dados.

Listagem 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

Ao digitar pela primeira vez o código na Listagem 1 no Visual Studio, você receberá muitas linhas onduladas vermelhas. Não criamos as classes GroupController ou Group.

Neste ponto, não podemos nem criar nosso aplicativo para que não possamos executar nosso primeiro teste de unidade. Isso é bom. Isso conta como um teste com falha. Portanto, agora temos permissão para começar a escrever o código do aplicativo. Precisamos escrever código suficiente para executar nosso teste.

A classe De controlador de grupo na Listagem 2 contém o mínimo de código necessário para passar no teste de unidade. A ação Index() retorna uma lista estaticamente codificada de Grupos (a classe Group é definida na Listagem 3).

Listagem 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

Listagem 3 – Models\Group.vb

Public Class Group

End Class

Depois de adicionarmos as classes GroupController e Group ao nosso projeto, nosso primeiro teste de unidade será concluído com êxito (consulte a Figura 2). Fizemos o trabalho mínimo necessário para passar no teste. É hora de comemorar.

Sucesso!

Figura 02: Sucesso! (Clique para exibir a imagem em tamanho real)

Criando grupos de contatos

Agora podemos passar para a segunda história de usuário. Precisamos ser capazes de criar novos grupos de contatos. Precisamos expressar essa intenção com um teste.

O teste na Listagem 4 verifica se chamar o método Create() com um novo Grupo adiciona o Grupo à lista de Grupos retornados pelo método Index(). Em outras palavras, se eu criar um novo grupo, então eu deverá ser capaz de obter o novo Grupo de volta da lista de Grupos retornados pelo método Index().

Listagem 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

O teste na Listagem 4 chama o método Create() do controlador de grupo com um novo grupo de contatos. Em seguida, o teste verifica se chamar o método Index() do controlador de grupo retorna o novo Grupo nos dados de exibição.

O controlador de grupo modificado na Listagem 5 contém o mínimo de alterações necessárias para passar no novo teste.

Listagem 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

O controlador de grupo na Listagem 5 tem uma nova ação Criar(). Essa ação adiciona um Grupo a uma coleção de Grupos. Observe que a ação Index() foi modificada para retornar o conteúdo da coleção de Grupos.

Mais uma vez, executamos a quantidade mínima de trabalho necessária para passar no teste de unidade. Depois de fazermos essas alterações no controlador de grupo, todos os nossos testes de unidade serão aprovados.

Adicionando uma Validação

Esse requisito não foi declarado explicitamente na história do usuário. No entanto, é razoável exigir que um Grupo tenha um nome. Caso contrário, organizar contatos em Grupos não seria muito útil.

A listagem 6 contém um novo teste que expressa essa intenção. Esse teste verifica se a tentativa de criar um Grupo sem fornecer um nome resulta em uma mensagem de erro de validação no estado do modelo.

Listagem 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 atender a esse teste, precisamos adicionar uma propriedade Name à nossa classe Group (consulte Listagem 7). Além disso, precisamos adicionar um pouco de lógica de validação à ação Create() do controlador de grupo (consulte Listagem 8).

Listagem 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

Listagem 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 a ação Criar() do controlador de grupo agora contém validação e lógica de banco de dados. Atualmente, o banco de dados usado pelo controlador group consiste em nada mais do que uma coleção na memória.

Tempo de refatoração

A terceira etapa em Vermelho/Verde/Refatorar é a parte Refatorar. Neste ponto, precisamos recuar do nosso código e considerar como podemos refatorar nosso aplicativo para melhorar seu design. A fase Refatorar é o estágio em que pensamos muito sobre a melhor maneira de implementar princípios e padrões de design de software.

Somos livres para modificar nosso código de qualquer maneira que optarmos por melhorar o design do código. Temos uma rede de segurança de testes de unidade que nos impedem de quebrar a funcionalidade existente.

No momento, nosso controlador de grupo é uma bagunça da perspectiva do bom design de software. O controlador de grupo contém uma bagunça emaranhada de validação e código de acesso a dados. Para evitar violar o Princípio de Responsabilidade Única, precisamos separar essas preocupações em classes diferentes.

Nossa classe de controlador group refatorada está contida na Listagem 9. O controlador foi modificado para usar a camada de serviço ContactManager. Essa é a mesma camada de serviço que usamos com o controlador de contato.

A listagem 10 contém os novos métodos adicionados à camada de serviço ContactManager para dar suporte à validação, à listagem e à criação de grupos. A interface IContactManagerService foi atualizada para incluir os novos métodos.

A listagem 11 contém uma nova classe FakeContactManagerRepository que implementa a interface IContactManagerRepository. Ao contrário da classe EntityContactManagerRepository que também implementa a interface IContactManagerRepository, nossa nova classe FakeContactManagerRepository não se comunica com o banco de dados. A classe FakeContactManagerRepository usa uma coleção na memória como um proxy para o banco de dados. Usaremos essa classe em nossos testes de unidade como uma camada de repositório falsa.

Listagem 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

Listagem 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

Listagem 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

Modificar a interface IContactManagerRepository requer o uso para implementar os métodos CreateGroup() e ListGroups() na classe EntityContactManagerRepository. A maneira mais preguiçosa e mais rápida de fazer isso é adicionar métodos stub semelhantes a este:

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 fim, essas alterações no design do nosso aplicativo exigem que façamos algumas modificações em nossos testes de unidade. Agora precisamos usar o FakeContactManagerRepository ao executar os testes de unidade. A classe GroupControllerTest atualizada está contida na Listagem 12.

Listagem 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

Depois de fazermos todas essas alterações, mais uma vez, todos os nossos testes de unidade serão aprovados. Concluímos todo o ciclo de Red/Green/Refactor. Implementamos as duas primeiras histórias de usuários. Agora temos testes de unidade de suporte para os requisitos expressos nas histórias do usuário. Implementar o restante das histórias do usuário envolve repetir o mesmo ciclo de Red/Green/Refactor.

Modificando nosso banco de dados

Infelizmente, embora tenhamos atendido a todos os requisitos expressos por nossos testes de unidade, nosso trabalho não foi feito. Ainda precisamos modificar nosso banco de dados.

Precisamos criar uma nova tabela de banco de dados de grupo. Siga estas etapas:

  1. Na janela Servidor Explorer, clique com o botão direito do mouse na pasta Tabelas e selecione a opção de menu Adicionar Nova Tabela.
  2. Insira as duas colunas descritas abaixo no Designer Tabela.
  3. Marque a coluna ID como uma chave primária e a coluna Identidade.
  4. Salve a nova tabela com o nome Grupos clicando no ícone do disquete.

Nome da Coluna Tipo de Dados Permitir Nulos
ID INT Falso
Nome nvarchar (50) Falso

Em seguida, precisamos excluir todos os dados da tabela Contatos (caso contrário, não poderemos criar uma relação entre as tabelas Contatos e Grupos). Siga estas etapas:

  1. Clique com o botão direito do mouse na tabela Contatos e selecione a opção de menu Mostrar Dados da Tabela.
  2. Exclua todas as linhas.

Em seguida, precisamos definir uma relação entre a tabela de banco de dados Grupos e a tabela de banco de dados Contatos existente. Siga estas etapas:

  1. Clique duas vezes na tabela Contatos na janela servidor Explorer para abrir o Designer tabela.
  2. Adicione uma nova coluna de inteiro à tabela Contatos chamada GroupId.
  3. Clique no botão Relação para abrir a caixa de diálogo Relações de Chave Estrangeira (consulte Figura 3).
  4. Clique no botão Adicionar.
  5. Clique no botão de reticências exibido ao lado do botão Especificação de Tabela e Colunas.
  6. Na caixa de diálogo Tabelas e Colunas, selecione Grupos como a tabela de chaves primária e a ID como a coluna de chave primária. Selecione Contatos como a tabela de chaves estrangeiras e GroupId como a coluna de chave estrangeira (consulte a Figura 4). Clique no botão OK.
  7. Em Especificação INSERT e UPDATE, selecione o valor Cascata para Excluir Regra.
  8. Clique no botão Fechar para fechar a caixa de diálogo Relações de Chave Estrangeira.
  9. Clique no botão Salvar para salvar as alterações na tabela Contatos.

Criando uma relação de tabela de banco de dados

Figura 03: Criando uma relação de tabela de banco de dados (clique para exibir imagem em tamanho real)

Especificando relações de tabela

Figura 04: Especificando relações de tabela (clique para exibir imagem em tamanho real)

Atualizando nosso modelo de dados

Em seguida, precisamos atualizar nosso modelo de dados para representar a nova tabela de banco de dados. Siga estas etapas:

  1. Clique duas vezes no arquivo ContactManagerModel.edmx na pasta Modelos para abrir a entidade Designer.
  2. Clique com o botão direito do mouse na superfície Designer e selecione a opção de menu Atualizar Modelo do Banco de Dados.
  3. No Assistente de Atualização, selecione a tabela Grupos e clique no botão Concluir (consulte Figura 5).
  4. Clique com o botão direito do mouse na entidade Grupos e selecione a opção de menu Renomear. Altere o nome da entidade Grupos para Grupo (singular).
  5. Clique com o botão direito do mouse na propriedade de navegação Grupos exibida na parte inferior da entidade Contato. Altere o nome da propriedade de navegação Grupos para Grupo (singular).

Atualizando um modelo do Entity Framework do banco de dados

Figura 05: Atualizando um modelo do Entity Framework do banco de dados (Clique para exibir a imagem em tamanho real)

Depois de concluir essas etapas, seu modelo de dados representará as tabelas Contatos e Grupos. A entidade Designer deve mostrar ambas as entidades (consulte a Figura 6).

Entidade Designer exibindo Grupo e Contato

Figura 06: Entidade Designer exibindo Grupo e Contato (Clique para exibir imagem em tamanho real)

Criando nossas classes de repositório

Em seguida, precisamos implementar nossa classe de repositório. Ao longo dessa iteração, adicionamos vários novos métodos à interface IContactManagerRepository ao escrever código para satisfazer nossos testes de unidade. A versão final da interface IContactManagerRepository está contida na Listagem 14.

Listagem 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

Na verdade, não implementamos nenhum dos métodos relacionados ao trabalho com grupos de contatos em nossa classe EntityContactManagerRepository real. Atualmente, a classe EntityContactManagerRepository tem métodos stub para cada um dos métodos de grupo de contatos listados na interface IContactManagerRepository. Por exemplo, o método ListGroups() atualmente tem esta aparência:

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

    throw New NotImplementedException()

End Function

Os métodos stub nos permitiram compilar nosso aplicativo e passar nos testes de unidade. No entanto, agora é hora de realmente implementar esses métodos. A versão final da classe EntityContactManagerRepository está contida na Listagem 13.

Listagem 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

Criando os modos de exibição

ASP.NET aplicativo MVC ao usar o mecanismo de exibição de ASP.NET padrão. Portanto, você não cria exibições em resposta a um teste de unidade específico. No entanto, como um aplicativo seria inútil sem exibições, não podemos concluir essa iteração sem criar e modificar as exibições contidas no aplicativo Gerenciador de Contatos.

Precisamos criar as seguintes novas exibições para gerenciar grupos de contatos (consulte a Figura 7):

  • Views\Group\Index.aspx – exibe a lista de grupos de contatos
  • Views\Group\Delete.aspx – exibe o formulário de confirmação para excluir um grupo de contatos

A exibição Índice de Grupo

Figura 07: a exibição Índice de Grupo (Clique para exibir a imagem em tamanho real)

Precisamos modificar as seguintes exibições existentes para que elas incluam grupos de contatos:

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

Você pode ver as exibições modificadas examinando o aplicativo do Visual Studio que acompanha este tutorial. Por exemplo, a Figura 8 ilustra a exibição Índice de Contato.

A exibição Índice de Contatos

Figura 08: a exibição Índice de Contato (Clique para exibir a imagem em tamanho real)

Resumo

Nesta iteração, adicionamos uma nova funcionalidade ao nosso aplicativo Contact Manager seguindo uma metodologia de design de aplicativo de desenvolvimento orientada a testes. Começamos criando um conjunto de histórias de usuários. Criamos um conjunto de testes de unidade que corresponde aos requisitos expressos pelas histórias do usuário. Por fim, escrevemos código suficiente para atender aos requisitos expressos pelos testes de unidade.

Depois que terminamos de escrever código suficiente para atender aos requisitos expressos pelos testes de unidade, atualizamos nosso banco de dados e exibições. Adicionamos uma nova tabela Grupos ao nosso banco de dados e atualizamos nosso Modelo de Dados do Entity Framework. Também criamos e modificamos um conjunto de exibições.

Na próxima iteração, a iteração final, reescrevemos nosso aplicativo para aproveitar o Ajax. Aproveitando o Ajax, melhoraremos a capacidade de resposta e o desempenho do aplicativo Contact Manager.