Compartilhar via


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

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 aplicativo MVC ASP.NET gerenciamento de contatos (C#)

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

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

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

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

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

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

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

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

    }
}

Listagem 3 – Models\Group.cs

namespace ContactManager.Models
{
    public class Group
    {
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Listagem 8 – Controllers\GroupController.cs

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

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

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

namespace ContactManager.Controllers
{
    public class GroupController : Controller
    {

        private IContactManagerService _service;

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

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

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

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

Listagem 10 – Controllers\ContactManagerService.cs

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

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

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

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

Listagem 11 – Controllers\FakeContactManagerRepository.cs

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

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

        // Group methods

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

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

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

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

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

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

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

        #endregion
    }
}

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 Group CreateGroup(Group groupToCreate)
{
    throw new NotImplementedException();
}

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

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

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

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

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

        }

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

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

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

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

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

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

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

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

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 Explorer servidor 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 a Figura 3).
  4. Clique no botão Adicionar.
  5. Clique no botão de reticências que aparece 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árias e a ID como a coluna de chave primária. Selecione Contatos como a tabela de chave estrangeira 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 Cascade 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 a imagem em tamanho real)

Especificando relações de tabela

Figura 04: Especificando relações de tabela (clique para exibir a 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 o Designer de Entidade.
  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 a 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 que aparece 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 a 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.cs

using System.Collections.Generic;

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

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

Na verdade, não implementamos nenhum dos métodos relacionados ao trabalho com grupos de contatos. 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 IEnumerable<Group> ListGroups()
{
    throw new NotImplementedException();
}

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

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

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

        // Contact methods

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

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

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

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

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

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

        // Group Methods

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

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

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

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

        }

    }
}

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

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 os modos de exibição modificados 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 controlado por teste. 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 de terminarmos 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.