Iteração nº 6 – usar desenvolvimento controlado por testes (VB)
pela Microsoft
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):
- Gravar um teste de unidade que falha (vermelho)
- Escrever código que passa no teste de unidade (Verde)
- 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:
- O usuário pode exibir uma lista de grupos de contatos.
- O usuário pode criar um novo grupo de contatos.
- O usuário pode excluir um grupo de contatos existente.
- O usuário pode selecionar um grupo de contatos ao criar um novo contato.
- O usuário pode selecionar um grupo de contatos ao editar um contato existente.
- Uma lista de grupos de contatos é exibida na exibição Índice.
- 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 .
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.
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:
- 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.
- Insira as duas colunas descritas abaixo no Designer Tabela.
- Marque a coluna ID como uma chave primária e a coluna Identidade.
- 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:
- Clique com o botão direito do mouse na tabela Contatos e selecione a opção de menu Mostrar Dados da Tabela.
- 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:
- Clique duas vezes na tabela Contatos na janela servidor Explorer para abrir o Designer tabela.
- Adicione uma nova coluna de inteiro à tabela Contatos chamada GroupId.
- Clique no botão Relação para abrir a caixa de diálogo Relações de Chave Estrangeira (consulte Figura 3).
- Clique no botão Adicionar.
- Clique no botão de reticências exibido ao lado do botão Especificação de Tabela e Colunas.
- 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.
- Em Especificação INSERT e UPDATE, selecione o valor Cascata para Excluir Regra.
- Clique no botão Fechar para fechar a caixa de diálogo Relações de Chave Estrangeira.
- Clique no botão Salvar para salvar as alterações na tabela Contatos.
Figura 03: Criando uma relação de tabela de banco de dados (clique para exibir imagem em tamanho real)
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:
- Clique duas vezes no arquivo ContactManagerModel.edmx na pasta Modelos para abrir a entidade Designer.
- 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.
- No Assistente de Atualização, selecione a tabela Grupos e clique no botão Concluir (consulte Figura 5).
- 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).
- 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).
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).
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
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.
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.