Introdução ao Desenvolvimento Guiado por Testes

Veja neste artigo como aplicar o TDD (Test-Driven Development) no desenvolvimento de aplicações .NET guiadas por testes com o Visual Studio, além de como utilizar Mock para facilitar a simulação de dados para testes.

Fique por dentro

Este artigo é útil porque apresentará para o leitor a metodologia Test-Driven Development (TDD) e como aplicá-la em projetos .NET.

O TDD preza pela realização de testes no código antes mesmo de implementar as funcionalidades propriamente ditas, reduzindo a quantidade de erros, aumentando a qualidade do código e permitindo ao desenvolvedor conhecer melhor as regras de negócio que estão sendo implementadas.

Veremos também como utilizar a técnica de mock no apoio ao TDD, prática bastante útil em cenários onde é necessário realizar testes com dados fictícios, pois facilita a geração de objetos para testes a partir de modelos do domínio da aplicação.

Durante o ciclo de vida de um software, os requisitos inicialmente estabelecidos geralmente mudam, novos são adicionados e outros removidos.

Claro que isso não vem de hoje, mas tornou-se muito mais constante devido às novas possibilidades de produzirmos softwares para diversas plataformas, por exemplo.

Sempre estamos evoluindo e os estilos tradicionais de desenvolvermos novos produtos enfrentam desafios significativos, como a necessidade constante de facilitar a manutenção.

Realizar alterações em uma aplicação desenvolvida e prestes a entrar para o mercado, tradicionalmente, pode causar indisposição junto aos seus clientes de formas difíceis de serem previstas.

Como resultado, as organizações hesitam em fazer mudanças que são necessárias para o software, impedindo assim o seu melhor desempenho e consequentemente seu sucesso perante as organizações que dele dependam.

Neste quesito, no que diz respeito à mudança, é que entramos no tema deste artigo, cujo foco é trabalhar em testes com base na metodologia Test-Driven Development (TDD).

O intuito deste artigo é apresentar de forma prática e simples como podemos trabalhar numa aplicação baseando-se em testes para as funcionalidades que desejamos implementar, de forma a reduzir falhas no sistema.

Com TDD podemos aumentar a nossa produtividade, pois iremos reduzir a quantidade de depurações necessárias para detecção de erros, teremos uma melhor visualização do nosso código, tornando mais simples a leitura dele, dentre outras vantagens que refletem de forma direta na construção de um software de qualidade.

Para praticar os conceitos que serão apresentados aqui, criaremos uma aplicação simples de carrinho de compras, no qual estaremos vendo a utilização de um “simulador de banco de dados” conhecido por Moq, que auxilia na geração de dados fictícios para testes.

Uma prévia sobre TDD

Nos últimos anos, houve uma série de novidades em torno da metodologia de TDD e sua real eficácia com relação ao desenvolvimento das mais diversas aplicações. Mesmo com a desenvoltura que podemos encontrar nas técnicas do TDD, ainda existe muita relutância na sua utilização, principalmente relacionada aos prazos para desenvolvimento e entrega de projetos.

Ao trabalharmos com desenvolvimento baseado em testes, temos primeiramente a mudança do nosso foco, que neste momento passa a se voltar para tecnologias ágeis e práticas baseadas em testes como sendo as operações primárias para o desenvolvimento de sistemas complexos e de alto desempenho.

O Test Driven Development é uma metodologia de desenvolvimento avançado com foco na qualidade do software orientado a objetos, amplamente utilizado dentro da comunidade Agile. O que torna o TDD único é o seu foco na especificação do projeto, ao invés de ser na sua validação.

A abordagem TDD leva a uma melhor arquitetura de software de qualidade, melhorando a qualidade, simplicidade e capacidade de teste, enquanto aumenta a confiança no código e a produtividade da equipe.

Ao implementarmos essa metodologia, os testes utilizados são compostos de afirmações verdadeiras e falsas que indicam o comportamento correto com relação ao teste que está sendo usado no momento.

Nesta abordagem criamos testes automatizados de unidades antes mesmo de escrevermos o código específico. Ao escrevermos inicialmente os testes, estes não só nos ajudam na medição dos testes propriamente ditos, mas também forçam o desenvolvedor a se concentrar no conceito do design avançado desde o início.

Através do desenvolvimento de um conjunto de casos de teste funcional inicialmente, os desenvolvedores podemos ter a certeza de que terão sempre um software com o mínimo de falha possível, além de que as características recém-aplicadas serão aplicadas conforme o esperado.

Para o TDD, temos que uma unidade é definida basicamente como sendo uma classe ou um grupo de funções relacionadas, muitas vezes chamado de módulo. Com a disposição de unidades relativamente pequenas, podemos oferecer benefícios críticos à nossa aplicação, como é o caso da redução dos esforços no momento da depuração, além de que testes pequenos melhoram a legibilidade e compreensão mais rápida e eficiente do que é preciso fazer.

O ciclo do TDD: Red-Green-Refactor

O TDD é conduzido por uma filosofia básica, conhecida como "Red-Green-Refactor" (Figura 1), que é um microciclo de desenvolvimento iterativo no qual os testes e comportamentos são implementados.

Neste processo, o que ocorre primeiramente é que os casos de teste recém-escritos testam o código ainda não escrito e, devido a isso, falham. Esta é a primeira etapa, definida como Red.

A partir deste momento o desenvolvedor implementa o recurso da maneira mais simples, fazendo com que os testes correspondentes sejam bem-sucedidos, o que representa o segundo passo do ciclo, o Green.

E por fim, antes de começarmos com um novo teste, podemos realizar o processo de refatoração do código de forma a deixá-lo o mais claro e eficiente possível, de forma a proporcionar confiança pela capacidade de executar o teste novamente que foi implementado.

Este é o terceiro item do ciclo, a Refatoração (ou Refactor). Ao adotarmos o ciclo Red-Green-Refactor, temos que o nosso código pode evoluir para um nível superior de comunicação simplificada, confiança e produtividade global.

Figura 1.Ciclo RGR (Red-Green-Refactor).

Ao trabalharmos com base no ciclo TDD, somos levados a desenvolver de forma mais rápida, de forma que, ao termos uma alteração no código concluído, este será testado automaticamente. Isso mostra que se tivermos um erro, este será percebido quase que imediatamente.

O imediatismo e a localidade do feedback são muito valiosos. Um microciclo eficaz requer compilação e execução de casos de teste rápidos. Este requisito é fundamental para sustentar o ritmo de desenvolvimento e redução da impertinência de testes.

Uma técnica chave é permitir a execução automática de casos de teste como uma atividade pós-compilação, que combina construção e teste em uma única etapa de desenvolvimento. Para que possamos realizar as atividades de forma a seguir os conceitos do TDD, temos que seguir alguns passos simples:

  1. Criar um novo teste;
  2. Executar o teste garantindo que este irá falhar (sim, falhar);
  3. Escrever código suficiente para que o teste seja aprovado;
  4. Refatorar o código testado;
  5. Nos certificarmos de que ao refatorarmos, o código não seja quebrado;
  6. Repetir o processo até que todos os testes foram escritos e aprovados.

Estes são os seis passos que devemos ter em mente ao trabalharmos com base no ciclo do TDD para que possamos ter nossas aplicações dentro do padrão correto e mais simples possível.

Por que utilizar o TDD em nossas aplicações?

Como qualquer outra metodologia de desenvolvimento, o TDD exige uma mudança de paradigmas com foco no código para uma abordagem baseada em testes. Ao adotarmos uma abordagem de desenvolvimento orientada a testes, é necessário um esforço significativo, podendo requerer da organização de desenvolvimento uma mudança de cultura. Porém os benefícios referentes a estas mudanças podem ultrapassar os custos que podem ser acarretados. Esta mudança melhora a qualidade e a confiabilidade do sistema, maximiza a produtividade do desenvolvedor e acelera significativamente o tempo para a disponibilidade do projeto no mercado profissional. Vejamos a seguir alguns dos benefícios obtidos na adoção do TDD.

Mais qualidade no projeto

Com o TDD, cada componente é obrigado a ser construído de forma constante com capacidade a nível elevado de qualidade, de forma mais consistente com base nos testes, onde temos o desenvolvimento em pequenos incrementos, que exigem testes para serem aprovados.

Uma vez que o componente desenvolvido individualmente muda para a integração, este nível elevado de qualidade nos componentes leva a uma rápida integração e testes mais eficazes.

O esforço para construir uma ampla base de testes automatizados de forma incremental ao longo do ciclo de desenvolvimento, produz uma base muito mais completa e valiosa de testes do que um esforço em testes apressados, e muitas vezes “espremidos” no final do projeto. Durante todo o ciclo de desenvolvimento o foco maior em testes ajuda a manter um foco maior sobre a qualidade em geral.

Este investimento contínuo em qualidade rende benefícios significativos ao nível geral do sistema.

Código mais simples

Durante a implementação de um recurso, o desenvolvedor orientado a testes escreve o código mais simples para fazer os testes passarem. O TDD ajuda aos desenvolvedores a evitarem o excesso de soluções engenhosas devido ao seu microciclo de desenvolvimento. Ela também ajuda a quebrar o problema em unidades modulares e produzir uma solução que possa ser testada, bem como implementada. Esta abordagem para o desenvolvimento tem o efeito de simplificação no código do projeto e alcança os benefícios associados a ela, incluindo melhoria na legibilidade e compreensibilidade. Quanto mais complexo o problema, mais importante é garantir que cada unidade está codificada de uma forma simples tanto quanto possível.

Redução do tempo para disponibilizar o produto

O ciclo do TDD é mais curto que o ciclo de desenvolvimento tradicional. Concentrando-se em desenvolver apenas o que é necessário para a aprovação nos testes, usando esse feedback imediato para reduzir os erros de regressão, o ciclo de desenvolvimento pode ser rapidamente reduzido ao seu núcleo essencial.

Normalmente, os atrasos de integração para os sistemas aumentarão com o tempo de desenvolvimento do software e estenderão o cronograma das entregas. Ao aplicarmos o TDD, temos a maioria dos problemas de componentes resolvidos muito mais cedo no processo, diminuindo assim o tempo de integração e a rapidez para o produto completo estar no mercado.

Mais flexibilidade no projeto

A realidade atual de condução de negócios no ramo de softwares inclui a concorrência acirrada, prazos reduzidos e mais apertadas janelas de mercado. As oportunidades não esperam por momentos convenientes para emergir.

O TDD aumenta a capacidade de uma organização com base no desenvolvimento de software para responder rapidamente às mudanças de requisitos ou mesmo às atualizações de produto imprevistas, facilitando os ciclos de desenvolvimento e de integração mais curtos e apoiando o rápido aperfeiçoamento de novas exigências.

Uma cultura sólida de TDD com uma rica base de teste é a melhor preparação para que se possa aproveitar rapidamente as oportunidades e vencer a concorrência no mercado.

Para que possamos nos familiarizar com alguns dos recursos oferecidos pelo TDD, estaremos desenvolvendo aplicações simples no decorrer do artigo, onde o nosso primeiro exemplo será referente a operações matemáticas e, em seguida, teremos uma aplicação mais realista, na qual faremos uma simulação de operações de inserção numa base de dados. Para os nossos projetos, estaremos trabalhando com o Visual Studio 2015.

Para darmos início à nossa aplicação inicial, criaremos um novo projeto do tipo ASP.NET Web Application e a ele daremos o nome de DevmediaTDD, como mostra a Figura 2. Em seguida, na tela que será apresentada, selecionaremos um template vazio e marcaremos a opção “Add Unit Tests”, o qual deixaremos com o mesmo nome do projeto, como apresentado pela Figura 3.

Figura 2. Criando um novo projeto ASP.NET.
Figura 3. Selecionando o Template.

Agora que temos nosso projeto criado, realizaremos algumas configurações para que possamos trabalhar com o NUnit, que é um framework de testes de unidade para todas as linguagens .NET. Este framework é escrito em C# e foi completamente redesenhado para tirar proveito dos muitos recursos do .NET, como os atributos personalizados. No período em que esse artigo foi escrito, o NUnit 3.0 está atualmente em desenvolvimento, apresentando uma nova infraestrutura e novos recursos. Trabalharemos então com a versão beta disponível.

Para adicionarmos o NUnit ao nosso projeto, devemos clicar com o botão direito sobre o projeto DevmediaTDD.Tests e selecionar a opção Manage NuGet Packages. Em seguida, buscaremos por NUnit, como mostra a Figura 4, e ao encontrarmos o pacote desejado, o instalaremos. Ao finalizarmos a instalação, percebam que a referência foi adicionada ao projeto, como mostra a Figura 5.

Figura 4. Instalando o NUnit.
Figura 5. Visualizando o NUnit nas referências do projeto.

Perceba na Figura 4, que com a nova versão do Visual Studio, o gerenciador NuGet apresenta de melhor forma as informações sobre os pacotes que estamos precisando. Além disso, temos a versão do pacote do NUnit que estamos instalando, que é uma versão Beta. Para finalizarmos com as atualizações do ambiente, selecionaremos no menu “Tools” a opção “Extensions and Updates” para adicionarmos o NUnit Test Adapter, como mostram as Figuras 6 e 7. Com esta extensão podemos rodar nossos testes dentro do Visual Studio.

Figura 6. Selecionando a opção Extensions and Updates.
Figura 7. Habilitando o NUnit TestAdapter.

Para habilitarmos o NUnit Test Adapter, precisamos fazer o download dele para a aplicação. No nosso caso, as opções que são apresentadas são a de Disable ou Uninstall, devido ao fato de já termos ele instalado.

Com o nosso ambiente configurado, iremos agora utilizar a classe UnitTest1, que foi criada automaticamente. Nesta classe, faremos inicialmente uma modificação, que é a de usarmos o NUnit Framework, adicionando o “using NUnit.Framework”. Esta classe por padrão, traz configurações do MSUnit, outro framework de testes, mas que não utilizaremos neste artigo. Vejamos então de acordo com a Listagem 1 como ficará a nossa classe inicialmente.

Listagem 1. Classe UnitTest1.cs.
using System; using NUnit.Framework; namespace DevmediaTDD.Tests { [TestFixture] public class UnitTest1 { [Test] public void TestMethod1() { } } }

Dentre os recursos oferecidos pelo NUnit, temos o atributo TestFixture, que é utilizado para indicar que uma classe contém métodos de teste. Quando adicionamos este atributo para uma classe no projeto, a aplicação Test Runner irá buscar pelos métodos de testes. Além dele, também temos o atributo [Test] que é usado para indicar que um método dentro da aplicação deve ser executado pelo Test Runner. Alteremos então a nossa classe de testes para realizar algumas operações matemáticas, como apresentado de acordo com a Listagem 2.

Listagem 2. Teste de operações matemáticas.
namespace DevmediaTDD.Tests { [TestFixture] public class UnitTest1 { [Test] public void soma() { Assert.AreEqual(12, 10 + 2); } [Test] public void subtracao() { Assert.AreEqual(5, 8 - 3); } [Test] public void multiplicacao() { Assert.AreEqual(15, 5*2); } } }

Antes de executarmos nossa aplicação, perceba que um novo item foi adicionado, que foi o “Assert”, onde temos que o NUnit nos fornece um rico conjunto de afirmações como métodos estáticos da classe Assert. Alguns dos métodos presentes nesta classe são o AreEqual, AreNotEqual, Contains, Greater, IsFalse, dentre outros.

Com nossa classe criada, temos agora que testar e ver os resultados. Para que possamos executar os testes, devemos selecionar na barra de tarefas o item Test e em seguida, Run e por último, colocar todos os testes para serem executados, como podemos ver na Figura 8.

Figura 8. Executando todos os testes.

Ao realizarmos os testes, devemos obter os resultados como esperado, de acordo com a Figura 9, onde temos que duas das operações foram bem-sucedidas, enquanto que uma apresentou erro, no caso, o método de multiplicação.

Figura 9. Visualização do resultado no Test explorer.

Como podemos observar nessa figura, temos os métodos que foram aprovados e o que foi reprovado. Podemos clicar em cima de cada um e ver na guia que aparece abaixo as informações referentes ao processo. No nosso caso, selecionamos a opção que apresenta o erro, e nela vemos que a mensagem que é apresentada é que o valor esperado seria 15, mas o que retornou foi 10. No StackTrace(), é apresentado um link no qual, se clicamos, somos levados diretamente à linha na qual se encontra o erro.

Criando um projeto de Carrinho de compras

Agora que tivemos essa pequena introdução ao uso do NUnit, iremos criar um novo exemplo que será referente à criação de um carrinho de compras bem simples, no qual teremos algumas das operações básicas.

Começaremos então criando um novo projeto Web Application, o qual chamaremos de CarrinhoCompras, em seguida selecionaremos a opção de template “Empty” e marcaremos a opção de criar classe de testes, como fizemos no exemplo anterior.

Ao iniciarmos o projeto, primeiramente apagaremos a classe que foi criada automaticamente e em seguida adicionaremos uma nova classe, a qual chamaremos de ItensCarrinho. Esta classe será responsável por testarmos os itens que serão incluídos no carrinho, dentre as informações, temos a quantidade de itens e a descrição, além do preço referente à quantidade de produtos, como podemos ver na Listagem 3.

Listagem 3. Criação da classe de testes ItensCarrinho.
using System; using NUnit.Framework; namespace CarrinhoCompras.Tests { [TestFixture] public class ItensCarrinho { private int _quantidade; private string _descricao; private ItensCarrinho _itemCarrinho; private decimal _precoUnitario; private decimal _precoItem; [SetUp] public void SetUp() { _quantidade = 2; _precoUnitario = 3.00m; _precoItem = _quantidade* _precoUnitario; _descricao = "Teste de descrição do produto"; _itemCarrinho = new ItensCarrinho(_quantidade, _descricao, _precoUnitario); } [Test] public void QtdItens() { Assert.AreEqual(_itemCarrinho.Quantidade, _quantidade); } [Test] public void DescricaoItem() { Assert.AreEqual(_itemCarrinho.Descricao, _descricao); } [Test] public void PrecoUnitario() { Assert.AreEqual(_itemCarrinho.PrecoUnitario, _precoUnitario); } [Test] public void ValorProdutoQtd() { var valorAtual = _itemCarrinho.PrecoQtdProduto(); Assert.AreEqual(valorAtual, _precoItem); } } }

Podemos perceber nessa listagem um novo atributo utilizado, que é o [SetUp], o qual é aplicado dentro do TestFixture para fornecer um conjunto comum de funções que serão executadas antes que cada método de teste seja executado. Algo que devemos observar neste ponto é que para cada TextFixture, deve haver apenas um [SetUp], pois caso haja mais que isso, ele compilará normalmente, mas os testes não serão executados.

Outro ponto a ser notado aqui é com relação à criação de uma instância da classe ItensCarrinho, a qual foi criada dentro do projeto CarrinhoCompra, definida de acordo com a Listagem 4.

Listagem 4. Criação da classe ItensCarrinho.
namespace CarrinhoCompras { public class ItensCarrinho { public int Quantidade { get; set; } public string Descricao { get; set; } public decimal PrecoUnitario { get; set; } public ItensCarrinho(int qtd, string desc, decimal precoUnitario) { Quantidade = qtd; Descricao = desc; PrecoUnitario = precoUnitario; } // Método responsável por realizar o cálculo do valor do produto pela quantidade de itens. public decimal PrecoQtdProduto() { return Quantidade* PrecoUnitario; } } }

Para darmos continuidade, criaremos uma nova classe de testes, clicando com o botão direito no projeto CarrinhoCompras.Tests e selecionando a opção de adicionar um novo item. Na tela que será apresentada, selecionaremos a opção Test Class e daremos o nome de CarrinhoTests, como mostra a Figura 10.

Figura 10. Adicionando uma nova classe de testes.

Nesta nova classe de testes, faremos algumas validações, como é o caso de adição de um mesmo item de compra no carrinho, valor total de compra, dentre outras situações que podemos precisar. O código da nossa classe de testes CarrinhoTests é apresentada na Listagem 5.

Listagem 5. Criação da classe de testes CarrinhoTests.cs.
using System; using System.Text; using System.Collections.Generic; using NUnit.Framework; namespace CarrinhoCompras.Tests { [TestFixture] public class CarrinhoTests { private Carrinho _carrinho; private ItensCarrinho _itens; private ItensCarrinho _itens2; [SetUp] public void SetUp() { _carrinho = new Carrinho(); _itens = new ItensCarrinho(3, "Testes", 8.00m); _itens2 = new ItensCarrinho(4, "Testes 2", 6.00m); } [Test] public void CarrinhoComZeroItems() { Assert.AreEqual(_carrinho.itens.Count, 0); } [Test] public void AdicionaItemCarrinho() { _carrinho.AdicionaItem(_itens); Assert.That(_carrinho.itens, Has.Member(_itens)); } [Test] public void ContemItemDuplicado() { _carrinho.AdicionaItem(_itens); _carrinho.AdicionaItem(_itens); Assert.That(_carrinho.itens.Count, Is.EqualTo(1)); } [Test] public void RemoveItemCarrinho() { _carrinho.AdicionaItem(_itens); _carrinho.RemoveItem(_itens); Assert.That(_carrinho.itens.Count, Is.EqualTo(0)); } [Test] public void ValorTotalCarrinho() { _carrinho.AdicionaItem(_itens); _carrinho.AdicionaItem(_itens2); var _somaProdutos = _carrinho.valorTotalCompras(); Assert.That(_somaProdutos, Is.EqualTo(48.00m)); } } }

Para que possamos testar o carrinho de compras, precisamos da classe Carrinho, que deve ser criada no projeto CarrinhoCompras. Esta classe é representada pelo código apresentado pela Listagem 6. Percebam neste caso que temos a apresentação de um novo item do grupo dos Asserts, que é o Assert.That, que é bem semelhante ao Assert.AreEqual.

Listagem 6. Criação da classe Carrinho.cs.
using System.Collections.Generic; namespace CarrinhoCompras { public class Carrinho { public List<ItensCarrinho> itens { get; set; } public Carrinho() { itens = new List<ItensCarrinho>(); } public void AdicionaItem(ItensCarrinho item) { // Verifica se a lista já contém um item igual adicionado if (itens.Contains(item)) { return; } itens.Add(item); } public void RemoveItem(ItensCarrinho item) { itens.Remove(item); } public decimal valorTotalCompras() { decimal ValorTotal = 0m; foreach(var item in itens) { ValorTotal += item.PrecoQtdProduto(); } return ValorTotal; } } }

Após termos a noção de como podemos trabalhar com testes em classes simples, veremos agora na nossa aplicação uma simulação de inserção de dados numa base de dados. Para isso, trabalharemos com o framework Moq e realizaremos algumas modificações nas nossas classes criadas até o momento.

De forma bem simples, temos que os objetos mock são uma ótima maneira de simularmos um objeto no qual precisamos trabalhar. Esses objetos podem ser utilizados para que possamos simular diversas situações, como erros de rede, ou mesmo uma falha específica no banco de dados.

Outro grande benefício sobre os frameworks de simulação é que os dados externos que podemos estar utilizando não serão afetados por nenhuma alteração realizada. Podemos simplesmente simular uma chamada ao banco de dados e não afetar o nosso banco de dados real.

Ao trabalharmos com testes unitários, precisamos escrever testes que cobrem uma área específica de código, neste momento, o código ou classe em teste pode interagir com outras classes ou sistemas externos, mas nós só precisamos isolar a classe que estamos trabalhando.

Quando trabalhamos com TDD, ele se torna de grande ajuda, pois evita que precisemos utilizar dados reais para testes. O Moq é um framework open source e adota uma API que tenta ser ao mesmo tempo simples de aprender e fácil de utilizar. A API também segue o estilo Arrange-Act-Assert e se baseia fortemente nos recursos do .NET, como expressões lambda e métodos de extensão.

Quando nos referimos ao estilo Arrange-Act-Assert, temos que ter em mente o que cada um desses processos significa. Começamos por Arrange (organizar), que é o primeiro passo a ser visto numa aplicação de teste da unidade.

Neste ponto, temos a organização do teste, onde trataremos das configurações necessárias para que ele possa ser executado, significando que para realizarmos o teste, precisaremos inicialmente criar um objeto da classe que terá o foco de nossas atenções.

O outro ponto é o Act (agir), que é o passo intermediário de uma aplicação e é onde executaremos o nosso teste. Por último, temos o Assert (afirmação), neste ponto iremos verificar o resultado retornado juntamente com o resultado esperado e ver se são compatíveis ou não.

Para começarmos a trabalhar com o Moq Framework, iremos adicioná-lo ao nosso projeto utilizando novamente o NuGet, onde buscaremos por Moq e realizaremos a instalação, como mostra a Figura 11.

Figura 11. Instalação do Moq framework.

Com o Moq instalado, precisaremos agora realizar algumas modificações referentes às nossas classes, onde precisaremos criar as interfaces necessárias para a utilização desse framework em nosso projeto. Dito isso, a nossa interface para inserção de dados no carrinho de compras será definida conforme a Listagem 7.

Listagem 7. Criação da interface ICarrinhoDatabase.
namespace CarrinhoCompras { public interface ICarrinhoDatabase { long InsereCarrinho(Carrinho _carrinho); } }

Na nossa classe em questão, temos apenas um método chamado InsereCarrinho, no qual temos a classe Carrinho sendo utilizada para que possamos recuperar suas informações. Em seguida, criaremos nossa classe de testes que irá utilizar a nossa interface juntamente com o Moq Framework, como podemos ver na Listagem 8.

Listagem 8. Criação da classe CarrinhoRepositórioTests.cs.
using NUnit.Framework; using Moq; namespace CarrinhoCompras.Tests { [TestFixture] public class CarrinhoRepositorio { private MockRepository _mockRepositorio; private Mock<ICarrinhoDatabase> _carrinhoBancoDados; private Carrinho carrinho; [SetUp] public void SetUp() { _mockRepositorio = new MockRepository(MockBehavior.Strict); _carrinhoBancoDados = _mockRepositorio.Create<ICarrinhoDatabase>(); carrinho = new Carrinho(); } [Test] public void SalvarCarrinho() { _carrinhoBancoDados.Setup(c => c.InsereCarrinho(carrinho)).Returns(1001); var repositorio = new carrinhoDBRepositorio(_carrinhoBancoDados.Object); var resultado = repositorio.salvaCarrinho(carrinho); Assert.AreEqual(resultado, 1001); } [Test] public void ErroAoSalvarCarrinhoCompras() { _carrinhoBancoDados.Setup(c => c.InsereCarrinho(carrinho)).Throws<ApplicationException>(); var repositorio = new carrinhoDBRepositorio(_carrinhoBancoDados.Object); var resultado = repositorio.salvaCarrinho(carrinho); Assert.AreEqual(resultado, 0); } } public class carrinhoDBRepositorio { private ICarrinhoDatabase _carrinhoDB; public carrinhoDBRepositorio(ICarrinhoDatabase carrinhoBancoDados) { _carrinhoDB = carrinhoBancoDados; } public long salvaCarrinho(Carrinho carrinhoCompras) { long retorno; try { retorno = _carrinhoDB.InsereCarrinho(carrinhoCompras); } catch (Exception ex) { retorno = 0; } return retorno; } } }

Nessa classe temos dois métodos de testes, que são o de ErroAoSalvarCarrinhoCompras que irá levantar uma exceção quando o valor retornado for zero, e o SalvarCarrinho, referente ao salvamento dos dados do carrinho.

Caso todas as informações estejam corretas, o resultado esperado é que seja apresentado o código de inserção que estamos representando pelo valor 1001.

Perceba também que nesta classe definimos a classe pública chamada carrinhoDBRepositorio, na qual temos definido o método público SalvaCarrinho, que verifica dentro de um Try...Catch se o retorno das operações foi nulo ou se retornou o valor correto.

Um ponto importante a ser destacado nessa classe é a utilização do Mock<ICarrinhoDatabase>, que é utilizado para inicializar uma instância do Mock por default. Além dele, temos o MockRepository, que é uma classe repositório utilitária utilizada para construir múltiplas simulações para uma verificação.

Esta classe repositório ajuda no cenário de várias simulações serem criadas durante um teste, fornecendo uma criação simplificada dessas várias simulações com um MockBehavior padrão.

O MockBehavior, por sua vez, é um Enum que apresenta as opções para personalizarmos o comportamento da nossa simulação. No nosso caso utilizamos o Strict, que faz com que a simulação lance uma exceção sempre que as chamadas não tiverem uma configuração correspondente.

As outras opções do Behavior que podem ser utilizadas são a Loose e a Default. A Loose, nunca vai lançar exceções, retornando valores padrão quando necessário, de igual forma ao Default.

Com toda essa etapa tendo sido vista, iremos trabalhar com um novo objeto no nosso domínio, que será o Cliente que poderá ser tanto um cliente comum quanto um cliente especial. Primeiramente, criaremos a nossa classe de testes para o projeto, a qual será chamada de ClienteComprasTests. Para essa classe teremos o código que será parecido com o apresentado na Listagem 9.

Listagem 9. Criando a classe de testes ClienteComprasTests.
using NUnit.Framework; using Moq; using System.Linq; namespace ClienteComprasTestes.Tests { [TestFixture] public class ClienteTests { public IClienteRepositorio mockClienteRepositorio { get; private set; } [Test] public void ClienteCarrinho() { // Criando alguns dados de teste IList<Cliente> clientes = new List<Cliente> { new Cliente { ClienteId = 1, Nome = "Edson Dionisio", Email = "edson.dionisio@gmail.com", Telefone = "8197402803", Endereco = "Rua dos testes Devmedia", Profissao = "Desenvolvedor web", DataCadastro = now() }, new Cliente { ClienteId = 2, Nome = "Marilia Kessia", Email = "mkessia@gmail.com", Telefone = "8197499820", Endereco = "Rua dos testes Devmedia", Profissao = "Professora de lingua inglesa", DataCadastro = now() }, new Cliente { ClienteId = 3, Nome = "Maitê Dionisio", Email = "maite.dionisio@gmail.com", Telefone = "8197499820", Endereco = "Rua dos testes Devmedia", Profissao = "Analista de softwares", DataCadastro = now() }, new Cliente { ClienteId = 4, Nome = "Maitê Dionisio", Email = "maite.dionisio@gmail.com", Telefone = "8197499820", Endereco = "Rua dos testes Devmedia", Profissao = "Analista de softwares", DataCadastro = now() }, new Cliente { ClienteId = 5, Nome = "Tatsu Yamashiro", Email = "tatsu.yamashiro@gmail.com", Telefone = "8197490000", Endereco = "Rua dos testes Devmedia", Profissao = "Designer de aplicações", DataCadastro = now() } }; // Simulação com Moq para o repositório Mock<IClienteRepositorio> mockClienteRepositorio = new Mock<IClienteRepositorio>(); // Retornando todos os clientes mockClienteRepositorio.Setup(clienteRepo => clienteRepo.BuscarTodos()).Returns(clientes); // Retornando os clientes com base no código mockClienteRepositorio.Setup(clienteRepo => clienteRepo.BuscaPorId(It.IsAny<int>())).Returns((int i) => clientes.Where(c => c.ClienteId == i).Single()); // Retornando os clientes por nome mockClienteRepositorio.Setup(cliente => cliente.FindByNome(It.IsAny<string>())).Returns((string s) => clientes.Where(c => c.Nome == s).Single()); // Verificação para a adição do cliente mockClienteRepositorio.Setup(cliente => cliente.Salvar(It.IsAny<Cliente>())).Returns( (Cliente cliente) => { DataCadastro Data = DateTime.Now; if (cliente.ClienteId.Equals(default(int))) { cliente.ClienteId = clientes.Count() + 1; clientes.Add(cliente); } else { var original = clientes.Where(c => c.ClienteId == cliente.ClienteId).Single(); if (original == null) { return false; } original.ClienteId = cliente.ClienteId; original.Nome = cliente.Nome; original.Email = cliente.Email; original.Telefone = cliente.Telefone; original.Endereco = cliente.Endereco; original.Profissao = cliente.Profissao; original.DataCadastro = cliente.DataCadastro; } return true; }); this.mockClienteRepositorio = mockClienteRepositorio.Object; }

Como podemos perceber nessa listagem, temos inicialmente a criação de um teste para listar os clientes que estão na “base de dados”.

Foram passados alguns dados fictícios para que pudéssemos realizar este teste e criamos dois meios de pesquisa, os quais são tanto por ID do cliente quanto pelo nome do mesmo. Nesse exemplo percebemos a introdução de um novo item pertencente ao Moq, que é o método IsAny<T>, o qual é responsável por testar se algum dos valores fornecidos equivale aos dados pesquisados.

Outro item apresentado que faz parte do Moq Framework é o Setup, que tem por finalidade especificar uma configuração do tipo simulada (mock) através de uma chamada para um método de valor de retorno. Continuando com a nossa classe de testes, adicionaremos novos testes que serão referentes ao retorno dos clientes, cujo código pode ser visto na Listagem 10.

Listagem 10. Métodos de teste para retorno de informações do cliente.
[Test] public void RetornarClientePorNome() { // Encontrar cliente por nome Cliente clienteRetorno = this.mockClienteRepositorio.FindByNome("Edson Dionisio"); Assert.IsNotNull(clienteRetorno); Assert.AreEqual(4, clienteRetorno.ClienteId); } [Test] public void TodosClientesRetornar() { // Buscar por todos os clientes IList<Cliente> clienteRetorno = this.mockClienteRepositorio.BuscarTodos(); Assert.IsNotNull(clienteRetorno); Assert.AreEqual(5, clienteRetorno.Count); }

Temos então dois métodos responsáveis pela apresentação das informações do cliente, onde temos que no primeiro método retornamos as informações com base no nome do cliente, já no segundo retornamos todos os clientes. A seguir, teremos os dois últimos métodos da classe de testes, que são os métodos de inserção e edição de clientes, como podemos ver na Listagem 11.

Listagem 11. Criação dos métodos de testes para Inserção e edição de cliente.
[Test] public void ClientePodeSerInserido() { // Inserindo um novo cliente Cliente insereCliente = new Cliente { Nome = "Edson testes 2", Email = "edson.testes@testes.com", Telefone = "97402803", Endereco = "Rua dos testes", Profissao = "Testes", DataCadastro = now() }; // Salvando um novo cliente this.mockClienteRepositorio.Salvar(insereCliente); } [Test] public void ClientePodeSerAlterado() { // Buscando o cliente pelo Id Cliente clienteRetorno = this.mockClienteRepositorio.BuscaPorId(1); // Realizando a edição do nome do cliente para teste clienteRetorno.Nome = "Edson Dionisio 2"; // Salvando as alterações realizadas para o cliente this.mockClienteRepositorio.Salvar(clienteRetorno); // Aqui verificamos se todas as informações foram trazidas certas Assert.AreEqual("Edson Dionisio 2", this.mockClienteRepositorio.BuscaPorId(1).Nome); }

Com a nossa classe de testes definida, teremos agora que criar os nossos arquivos referentes à interface para o repositório, chamada de IClienteRepositorio, e a classe Cliente.cs. Vejamos então como a interface deverá ser, de acordo com a Listagem 12.

Listagem 12. Criação da interface IClienteRepositorio.
using System.Collections.Generic; namespace CarrinhoCompras { public interface IClienteRepositorio { IList<Cliente> BuscarTodos(); Cliente FindByNome(string clienteNome); Cliente BuscaPorId(int clienteId); bool Salvar(Cliente cliente); } }

Definimos o método Salvar() como sendo bool, pois ele retornará apenas se foi inserido ou não, apresentando true ou false. Por último, temos a criação da classe Cliente.cs, como podemos ver na Listagem 13.

Listagem 13. Criação da classe Cliente.cs.
namespace CarrinhoCompras { public class Cliente { public int ClienteId { get; set; } public string Email { get; set; } public string Endereco { get; set; } public string Telefone { get; set; } public string Nome { get; set; } public string Profissao { get; set; } public string DataCadastro { get; set; } } }

Com isso finalizamos o nosso exemplo, onde criamos as nossas classes de testes antes de interagirmos com nossas classes básicas e regras de negócio propriamente ditas. Claro que além das vantagens que podemos ter ao utilizarmos TDD, também temos desvantagens, onde a que podemos considerar mais crítica é aprender a desenvolver orientado a testes.

Neste momento, temos nossas atenções voltadas para a criação de nossos pequenos blocos de testes que constituem basicamente a ideia chave de como teremos aquela regra aplicada no nosso código real. Mas uma vez que entendemos o processo e nos acostumamos a trabalhar dessa forma, o nosso trabalho torna-se muito mais produtivo e bem avaliado pelos empregadores.

Com a utilização de testes automatizados, temos a confiança de desenvolvermos softwares de forma a termos uma aplicação com menos bugs e que possui uma maior confiabilidade e credibilidade no mercado. Além disso, temos também uma maior agilidade com relação a modificarmos um código que não conhecemos sem a apresentação de novos erros.

Como aqui temos a necessidade de testarmos pequenos blocos de código, a fim de fazê-los funcionar, temos nosso foco centrado apenas nesta ideia, onde a cada novo teste adicionado, não haverá uma falha sendo criada em conjunto.

À medida que nossos softwares vão crescendo, temos a necessidade de aplicarmos mais e mais recursos, que se não forem bem testados, poderão nos causar alguns transtornos ao produzir bugs que possam afetar o desempenho de uma empresa.

Eis uma das maiores razões de utilizarmos o Test-Driven Development, produzir softwares de qualidade e com baixo índice de falhas.

Links

Site oficial do NUnit

Site oficial do Moq Framework

Confira também

Artigos relacionados