Twitter
RSS

Test-Driven Development no .NET (na prática)

0
No post anterior, fiz uma introdução ao Test-Driven Development (TDD), falando resumidamente sobre seus conceitos e mostrando como funciona o NUnit Testing Framework. Neste post, pretendo falar mais sobre a metodologia TDD e mostrar um exemplo de sua aplicação na prática.

Com esses conhecimentos já posso sair desenvolvendo programas utilizando TDD? Infelizmente não funciona bem assim, apenas sabendo programar orientado a objeto, conhecendo a sintaxe, atributos e métodos do NUnit não é suficiente. É preciso ter em mente algumas técnicas utilizadas em TDD antes de sair desenvolvendo um sistema. Existem algumas técnicas que devem ser seguidas quando se desenvolve utilizando TDD que facilitam o entendimento, no entanto, conhecer essas técnicas é apenas um começo. Para melhorar a sua experiência e aptidão, você deve praticar, práticar e práticar. Segue uma imagem para ilustrar melhor essas técnicas.

Se ainda não está familiarizado com TDD, o que estou escrevendo pode soar um pouco estranho. Muitas pessoas passaram muito tempo ouvindo que deve-se primeiro considerar cuidadosamente o design das classes, codificá-las e então testar. O que o TDD sugere é uma abordagem completamente diferente, o processo é iniciado de trás para frente, tendo o teste em primeiro lugar. Dito de outra forma, você não escreve uma linha de código até que você tenha um teste pronto para a função. A seqüência sugerida para desenvolvimento usando TDD é a seguinte:

1. Escrever um teste.
2. Execute o teste. Não consegue compilar uma vez que o código ainda não existe mesmo! (Esta é a mesma coisa que falhar.)
3. Escrever um esboço do que se deseja testar, só para fazer o teste compilar.
4. Execute o teste. Ele deve falhar. (Se não falhar , então o teste não era muito bom.)
5. Implementar o código completo para o teste passar.
6. Execute o teste. Ela deverá passar. (Se não o fizer, volte um passo e tente novamente.)
7. Começar com um novo teste!

Antes de chegar na etapa 5, você estará utilizando um processo denominado Codificação por Intenção. Quando você pratica Codificação por Intenção, o código é escrito de cima para baixo em vez de baixo para cima, ou seja, primeiro você cria um código de acesso a rotina, depois você cria a rotina em si. Em vez de pensar, "Eu preciso de uma classe com estes métodos" basta escrever o código desejado, antes que a classe realmente exista. Se tentar compilar seu código, não conseguirá, o compilador não poderá encontrar a classe. Este é um bom sinal, pois como disse acima, a falta de compilar, conta como uma falha no teste. O que vai fazer aqui é experimentar a intenção do código que está escrito. Isto não só ajuda a produzir códigos bem testados, como resulta também em código que é mais fácil de ler, mais fácil de depurar e com uma melhor concepção.

No desenvolvimento de software, testes tradicionais foram pensados para verificar se um trecho de código foi escrito corretamente. Quando você fizer TDD, no entanto, os seus testes são utilizados para definir o comportamento de uma classe antes de escrever a mesma. Não vou arriscar dizer que este método é mais fácil do que as velhas formas, mas na minha experiência é muito melhor.

Vamos ver uma pequena amostra desta metodologia. Suponhamos que você necessita escrever o simples e famoso sistema de conta bancária. Esse sistema consiste em fazer uma classe que possa representar uma Conta, possuindo o nome do proprietário, o saldo, e o estado da conta. Também pode ser feitos Depósitos, Saques e rendimento de juros. Tudo isso em um sistema simples num projeto Console Application. Veja a imagem abaixo que ilustra melhor o exemplo.

Antes de criar efetivamente a classe Conta, deve-se criar uma classe de teste, esses testes devem estar de acordo com as funcionalidades que imaginamos (“ou vemos na imagem acima”) que a classe Conta irá representar, com o desenvolvimento dessa classe iniciamos o TDD, o nome da classe de teste neste exemplo será ContaTests. Agora que já temos em mente o que a classe ContaTests deve ser capaz de fazer, vamos criar os testes com o NUnit Framework, conforme mostra o código abaixo:

using System;
using NUnit.Framework;

namespace TDD.Banco
{
    [TestFixture]
    public class ContaTests
    {
        [Test]
        public void TestDeposito()
        {
            Conta conta = new Conta("Test");
            conta.Deposito(125.0);
            conta.Deposito(25.0);
            Assert.AreEqual(150.0, conta.Balanco);
        }

        [Test]
        public void TestSaque()
        {
            Conta conta = new Conta("Test");
            conta.Saque(125.0);
            conta.Saque(25.0);
            Assert.AreEqual(-150.0, conta.Balanco);
        }

        [Test]
        public void TestJuro()
        {
            Conta conta = new Conta("Test");
            conta.Deposito(100.0);
            conta.AplicarJuro();
            Assert.AreEqual(105.0, conta.Balanco);
        }

        [Test]
        public void TestEstado()
        {
            Conta conta = new Conta("Test");
            conta.Deposito(1100.0);
            Assert.AreEqual("Ouro", conta.Estado);
            conta.Saque(600.0);
            Assert.AreEqual("Cinza", conta.Estado);
            conta.Saque(600.0);
            Assert.AreEqual("Vermelho", conta.Estado);
        }
    }
}

Depois de escrever o código acima na classe ContaTests é hora de compilá-la. É óbviu que a classe de testes não irá compilar, uma vez que a não existi a classe Conta. Isto ilustra o principal princípio de TDD, não escrever qualquer código sem que se tenha um teste pronto para ele. Lembro, quando os seus códigos de teste não compilarem, isso contará como uma falha de teste, e será cumprida a primeira etapa de TDD. Agora vamos criar a classe Conta com código suficiente apenas para fazer os testes compilar.

using System;

namespace TDD.Banco
{
    public class Conta
    {
        private String estado;       
        private String proprietario;

        public Conta(string proprietario)
        {           
            this.proprietario = proprietario;
            estado = "";           
        }

        public double Balanco
        {
            get { return 0.0; }
        }

        public String Estado
        {
            get { return estado; }
            set { estado = value; }
        }

        public void Deposito(double valor)
        {           
        }

        public void Saque(double valor)
        {           
        }

        public void AplicarJuro()
        {           
        }

        private void AlteraEstado()
        {           
        }
    }
}

Logo após criarmos a classe Conta, podemos compilar o projeto que não terá mais erros. Com isso podemos rodar nossos testes através do NUnit GUI. Para rodar os testes, rode o executável do NUnit GUI e abra o arquivo executável do projeto Console Application que foi criado nesse exemplo de teste. Clicando em “Run”, os testes serão executados e a tela do NUnit ficará conforme a imagem abaixo.

Notamos que todos os testes falharam. Isso já era esperado, pois a classe Conta foi criada apenas para compilar o projeto e os testes. Como pode ser visto na imagem, o teste “TestDeposito” mostrou a seguinte mensagem de erro: "TestDeposito: expected: <150> but was <0>". Esse resultado serviu para concluir mais uma etapa do TDD criar o teste de um método e fazer ele falhar. A próxima etapa é justamente implementar o método Deposito da classe Conta para que o teste “TestDeposito” passe sem erro.

Notém que por uma questão prática estou disponibilizando os códigos com todos os testes e métodos criados, quando se utiliza TDD essa não é uma boa prática, deve-se criar um teste para uma determinada rotina, criar a rotina apenas para compilar, rodar o teste propositalmente para que ele falhe, implementar a rotina para que o teste passe, tudo isso nessa respectiva ordem. Sabendo disso, vejam o código abaixo, com os testes implementados para que os testes passem.

using System;

namespace TDD.Banco
{
    public class Conta
    {
        private String estado;
        private double balanco;
        private String proprietario;

        public Conta(string proprietario)
        {
            // Novas contas por default são 'Cinza' e o balanco é 0.0
            this.proprietario = proprietario;
            estado = "Cinza";
            balanco = 0.0;
        }

        public double Balanco
        {
            get { return balanco; }
        }

        public String Estado
        {
            get { return estado; }
            set { estado = value; }
        }

        public void Deposito(double valor)
        {
            balanco += valor;
            AlteraEstado();
            Console.WriteLine("Depositado {0:C} --- ", valor);
            Console.WriteLine(" Balanço = {0:C}", Balanco);
            Console.WriteLine(" Estado  = {0}", Estado);
            Console.WriteLine("");
        }

        public void Saque(double valor)
        {
            balanco -= valor;
            AlteraEstado();
            Console.WriteLine("Sacado {0:C} --- ", valor);
            Console.WriteLine(" Balanço = {0:C}", Balanco);
            Console.WriteLine(" Estado  = {0}", Estado);
            Console.WriteLine("");
        }

        public void AplicarJuro()
        {
            if (!String.Equals(Estado, "Vermelho"))
            {
                balanco += 0.05 * balanco;
                Console.WriteLine("Juros Aplicados --- ");
                Console.WriteLine(" Balanco = {0:C}", Balanco);
                Console.WriteLine(" Estado  = {0}", Estado);
            }
            else
            {
                Console.WriteLine("Juro não pode ser aplicado pelo Estado da conta.");
                Console.WriteLine(" Balanco = {0:C}", Balanco);
                Console.WriteLine(" Estado  = {0}", Estado);
            }
        }

        private void AlteraEstado()
        {
            if (balanco < 0.0)
                Estado = "Vermelho";
            else if (balanco < 1000.0)
                Estado = "Cinza";
            else
                Estado = "Ouro";
        }
    }
}

Com o código acima já implementado, ao rodar os testes no NUnit, todos deverão passar como mostra a imagem abaixo. Tudo isso por que agora focamos no problema do teste, efetuamos a programação visando um objetivo, passar nos testes, por isso a importância de testar tudo que é possível.

Agora temos a classe Conta implementada e uma classe de testes ContaTest garantindo que todos os métodos estão funcionando de acordo com o previsto. Então podemos dizer que o TDD se encerra por aqui? ERRADO. Ainda falta uma etapa muito importante dentro do TDD, é a etapa do Refactoring.

Refatoração (do inglês Refactoring) é o processo de modificar um sistema de software para melhorar a estrutura interna do código sem alterar seu comportamento externo. (Wikipedia)

Devemos fazer um Refactoring tanto na nossa classe de teste quanto na nossa classe Conta. Vamos começar com a classe de teste, no código dela podemos notar que existe várias duplicações. Para cada método criamos e instanciamos um objeto do tipo Conta. Com ajuda dos atributos do NUnit, podemos refatorar essa classe excluindo essas repetições em cada método e substituindo esse código pelos método Setup e TearDown. Com isso cada vez que um teste for executado antes passará pelo Setup e depois pelo TearDown. Confira o código abaixo.

using System;
using NUnit.Framework;

namespace TDD.Banco
{
    [TestFixture]
    public class ContaTests
    {
        private Conta conta;

        [SetUp]
        public void Setup()
        {
            conta = new Conta("Test");
        }

        [TearDown]
        public void TearDown()
        {
            conta = null;
        }

        [Test]
        public void TestDeposito()
        {           
            conta.Deposito(125.0);
            conta.Deposito(25.0);
            Assert.AreEqual(150.0, conta.Balanco);
        }

        [Test]
        public void TestSaque()
        {           
            conta.Saque(125.0);
            conta.Saque(25.0);
            Assert.AreEqual(-150.0, conta.Balanco);
        }

        [Test]
        public void TestJuro()
        {           
            conta.Deposito(100.0);
            conta.AplicarJuro();
            Assert.AreEqual(105.0, conta.Balanco);
        }

        [Test]
        public void TestEstado()
        {           
            conta.Deposito(1100.0);
            Assert.AreEqual("Ouro", conta.Estado);
            conta.Saque(600.0);
            Assert.AreEqual("Cinza", conta.Estado);
            conta.Saque(600.0);
            Assert.AreEqual("Vermelho", conta.Estado);
        }
    }
}

Após a refatoração, podemos rodar os testes e conferir que todos continuam funcionando. Então vamos partir para um refactoring na classe Conta. Ao implementar a classe conta podemos constatar que de acordo com o estado da conta é tomada uma decisão diferente para cada caso. Isso impacta em várias verificações em cada rotina que se aplica uma operação que necessita uma tomada de decisão de acordo com o estado. Portanto para um melhor design da classe e para facilitar possíveis alterações duranto o tempo de vida do projeto vamos aplicar a classe Conta o Desing Pattern State, conforme imagem abaixo.

Conforme podemos ver no modelo acima, o design da classe vai mudar, porém o objetivo da classe Conta vai continuar sendo o mesmo, permitindo fazer deposito, saque e rendimentos de juro. Neste caso vamos supor que outro desenvolvedor resolveu fazer essa mudança, ele não vai seguir as metodologias TDD, não vai criar testes para as novas implementações, vai apenas alterar o design. Com isso podemos ver se o sistema está mais seguro com os testes que foram implementados. Vejam o código abaixo após o refactoring.

Classe Conta:

using System;

namespace TDD.Banco
{
    public class Conta
    {
        private Estado estado;       
        private String proprietario;

        public Conta(string proprietario)
        {
            // Novas cotas por default são 'Cinza' e tem o balanco 0.0
            this.proprietario = proprietario;
            estado = new EstadoCinza(0.0, this);           
        }

        public double Balanco
        {
            get { return estado.Balanco; }
        }

        public Estado Estado
        {
            get { return estado; }
            set { estado = value; }
        }

        public void Deposito(double valor)
        {
            estado.Deposito(valor);
            Console.WriteLine("Depositado {0:C} --- ", valor);
            Console.WriteLine(" Balanço = {0:C}", Balanco);
            Console.WriteLine(" Estado  = {0}", Estado.GetType().Name);
            Console.WriteLine("");
        }

        public void Saque(double valor)
        {
            estado.Saque(valor);
            Console.WriteLine("Sacado {0:C} --- ", valor);
            Console.WriteLine(" Balanço = {0:C}", Balanco);
            Console.WriteLine(" Estado  = {0}", Estado.GetType().Name);
            Console.WriteLine("");
        }

        public void AplicarJuro()
        {
            estado.AplicarJuro();      
            Console.WriteLine("Juros Aplicados --- ");
            Console.WriteLine(" Balanco = {0:C}", Balanco);
            Console.WriteLine(" Estado  = {0}", Estado.GetType().Name);
            Console.WriteLine("");
        }
    }
}

Classe Estado:

using System;

namespace TDD.Banco
{
    public abstract class Estado
    {
        protected Conta conta;
        protected double balanco;

        protected double juro;
        protected double limiteMin;
        protected double limiteMax;
       
        public Conta Conta
        {
            get { return conta; }
            set { conta = value; }
        }

        public double Balanco
        {
            get { return balanco; }
            set { balanco = value; }
        }

        public abstract void Deposito(double valor);
        public abstract void Saque(double valor);
        public abstract void AplicarJuro();
    }
}

Classe EstadoVermelho:

using System;

namespace TDD.Banco
{
    public class EstadoVermelho : Estado
    {
        double taxaServico;
       
        public EstadoVermelho(Estado estado)
        {
            this.balanco = estado.Balanco;
            this.conta = estado.Conta;
            Initialize();
        }

        private void Initialize()
        {           
            juro = 0.0;
            limiteMin = -100.0;
            limiteMax = 0.0;
            taxaServico = 15.00;
        }

        public override void Deposito(double valor)
        {
            balanco += valor;
            AlteraEstado();
        }

        public override void Saque(double valor)
        {
            valor = valor - taxaServico;
            Console.WriteLine("Sem saldo para efetuar saque!");
        }

        public override void AplicarJuro()
        {
            // Não tem juro estado vermelho
        }

        private void AlteraEstado()
        {
            if (balanco > limiteMax)
            {
                conta.Estado = new EstadoCinza(this);
            }
        }

        public override String ToString()
        {
            return "Vermelho";
        }
    }
}

Classe EstadoCinza:

using System;

namespace TDD.Banco
{
    public class EstadoCinza : Estado
    {
        public EstadoCinza(Estado estado) :
            this( estado.Balanco, estado.Conta)
        {   
        }

        public EstadoCinza(double balanco, Conta conta)
        {
            this.balanco = balanco;
            this.conta = conta;
            Initialize();
        }       

        private void Initialize()
        {
            juro = 0.05;
            limiteMin = 0.0;
            limiteMax = 1000.0;           
        }

        public override void Deposito(double valor)
        {
            balanco += valor;
            AlteraEstado();
        }

        public override void Saque(double valor)
        {
            balanco -= valor;
            AlteraEstado();
        }

        public override void AplicarJuro()
        {
            balanco += juro * balanco;
            AlteraEstado();
        }

        private void AlteraEstado()
        {
            if (balanco < estado =" new"> limiteMax)
            {
                conta.Estado = new EstadoOuro(this);
            }
        }

        public override String ToString()
        {
            return "Cinza";
        }
    }
}

Classe EstadoOuro:

using System;

namespace TDD.Banco
{
    public class EstadoOuro : Estado
    {
        public EstadoOuro(Estado estado) :
            this(estado.Balanco, estado.Conta)
        {
        }

        public EstadoOuro(double balanco, Conta conta)
        {
            this.balanco = balanco;
            this.conta = conta;
            Initialize();
        }

        private void Initialize()
        {
            juro = 0.05;
            limiteMin = 1000.0;
            limiteMax = 1000000.0;
        }

        public override void Deposito(double valor)
        {
            balanco += valor;
            AlteraEstado();
        }

        public override void Saque(double valor)
        {
            balanco -= valor;
            AlteraEstado();
        }

        public override void AplicarJuro()
        {
            balanco += juro * balanco;
            AlteraEstado();
        }

        private void AlteraEstado()
        {
            if (balanco < 0.0)
            {
                conta.Estado = new EstadoVermelho(this);
            }
            else if (balanco < limiteMin)
            {
                conta.Estado = new EstadoCinza(this);
            }
        }

        public override String ToString()
        {
            return "Ouro";
        }
    }
}

Após terminarmos as implementações, podemos rodar os testes no NUnit e verificar se algum deles falhou, indicando que alguma alteração mecheu no funcionamento do sistema. Neste exemplo foi deixado propositalmente duas alterações para que fossem pegas pelos testes. Como mostra a imagem do NUnit abaixo.

Dois testes falharam durante a execução, o primeiro deles foi o “TestEstado” onde esperava uma string “Ouro” e veio como retorno um tipo Ouro. No segundo teste que falhou, o “TestSaque” era esperado -150 no balanco e retornou -125, isso devido a uma alteração na lógica da conta com estado Vermelho, só pode sacar se tiver fundos. Vamos corrigir os testes, para que eles fiquem adequados as novas alterações.

using System;
using NUnit.Framework;

namespace TDD.Banco
{
    [TestFixture]
    public class ContaTests
    {
        private Conta conta;

        [SetUp]
        public void Setup()
        {
            conta = new Conta("Test");
        }

        [TearDown]
        public void TearDown()
        {
            conta = null;
        }

        [Test]
        public void TestDeposito()
        {           
            conta.Deposito(125.0);
            conta.Deposito(25.0);
            Assert.AreEqual(150.0, conta.Balanco);
        }

        [Test]
        public void TestSaque()
        {
            conta.Deposito(200.0);
            conta.Saque(125.0);
            conta.Saque(25.0);
            Assert.AreEqual(50.0, conta.Balanco);
        }

        [Test]
        public void TestSaqueVermelho()
        {           
            conta.Saque(125.0);
            conta.Saque(25.0);
            Assert.AreEqual(-125.0, conta.Balanco);
        }

        [Test]
        public void TestJuro()
        {           
            conta.Deposito(100.0);
            conta.AplicarJuro();
            Assert.AreEqual(105.0, conta.Balanco);
        }

        [Test]
        public void TestEstado()
        {           
            conta.Deposito(1100.0);
            Assert.AreEqual("Ouro", conta.Estado.ToString());
            conta.Saque(600.0);
            Assert.AreEqual("Cinza", conta.Estado.ToString());
            conta.Saque(600.0);
            Assert.AreEqual("Vermelho", conta.Estado.ToString());
        }
    }
}

Uma dica importante quanto à metodologia de TDD é a seguinte: toda vez que você encontrar um bug, primeiramente crie um teste que faça o bug aparecer. Só então, escreva o código que vai corrigir o bug. Com isso, o seu conjunto de testes melhora com o tempo, fica mais completo, e você vai ter certeza que daquele ponto em diante que o bug nunca mais voltará despercebido. É o que foi feito no caso do teste “TestSaque”, foi criado um teste “TestSaqueVermelho” apenas para simular o erro e depois corrigi-lo.

Podem acreditar, depois de usar NUnit por um tempo, vocês vão ver a importância dos testes, principalmente depois de fazer grandes alterações no sistema: se todos os testes passarem, você pode ter certeza que não quebrou nada, ao invés de só rezar. Também vai ter certeza que não danificou funcionalidades implementadas por outros programadores, o que em um ambiente de produção é fundamental. Você se sente mais seguro para fazer alterações, por que sabe que possívelmente pegará os erros que podem vir a ocorrer.

Como puderam notar a classe Conta com o novo design acabou ficando dependente de outras classes como EstadoVermelho, EstadoCinza e EstadoOuro. Testes de unidade procuram testar classes de um sistema isoladamente. Classes em um sistema normalmente alcançam seus objetivos com a ajuda de outras. Não funcionam isoladamente, freqüentemente se comunicam com outros elementos da aplicação. Quando construímos um teste de unidade, um dos principais desafios é exatamente isolar a classe que está sendo testada, para que nenhuma outra classe do sistema seja envolvida no teste.

Uma solução eficaz é o uso de mock objects (objetos “de mentira” ou objetos substitutos), que permitem isolar as classes de um sistema de forma bastante simples. No nosso exemplo, a classe Conta depende diretamente da classe EstadoVermelho. Usar um mock object significa que, quando estivermos testando, ao invés de usarmos a classe EstadoVermelho, usaremos uma outra, que “finge” ser essa classe, mas é mais simples e mais fácil de ser usada durante os testes (além disso, temos total controle sobre ela, pois é criada especialmente para os testes).

Mas isso pretendo mostrar em um próximo post, refatornado a nossa classe de teste, permitindo o uso de mock objects e criando novas classes de testes para cada classe separadamente.

Até a próxima…


Test-Driven Development no .NET

4
Esse post apresenta a técnica de desenvolvimento orientado a testes, que tem como um de seus objetivos antecipar a identificação e correção de falhas durante o desenvolvimento. Test-driven development ou TDD é um novo paradigma("adoro essa palavra") de desenvolvimento onde primeiro são criados os testes e somente depois é escrito o código necessário para passar por eles. Muita gente deve estar se perguntando: "Mas como vou fazer os testes se não tenho o código para testar?". Vamos tentar esclarecer essas dúvidas no decorrer desse post.

Antes de inicar o desenvolvimento utilizando TDD, deve-se ter o entendimento do que é Unit Test. Em termos práticos, Unit Test (Testes Unitários), significa escrever trechos de código que provem verdadeiro todas as categorias em sua aplicação, ou seja, testar se o objetivo do método está sendo atendido, verificar se a rotina não está sensível a um bug. São apenas testes que garantem que os métodos executam sem erros o que se espera deles.

Embora muitos dos desenvolvedores façam testes unitários em seus códigos, geralmente os testes são desenvolvidos/realizados após o código ser concebido e escrito. Como um grande número de desenvolvedores escreve os códigos antes dos testes, os testes acabam sendo viciosos e difícil de fazer muitas variações devido ao curto tempo. Desenvolvimento orientado a testes (TDD) tenta resolver este problema para gerar maior qualidade no código, evitando que se coloque a carroça na frente dos bois, redigindo os testes antes que escrever o código.

Uma das práticas de Extreme Programming (XP), TDD está adquirindo uma forte comunidade em Java, mas muito pouco foi escrito sobre como fazer em .NET. Ainda assim, foi desenvolvido para .NET, o NUnit Testing Framework, um framework de testes baseado no JUnit da comunidade Java, que serve para executar e apresentar o resultado dos testes. Como ele faz isso? O Test Runner do NUnit faz uma busca no código compilado, procurando por atributos que identifiquem as classes e métodos como de teste. Em seguida, através do uso do Reflection, ele executa essas classes e métodos que foram considerados como de testes. Para informar que a classe é uma classe de testes, não é preciso que ela derive de uma classe base de testes, somente é necessário que sejam utilizados os atributos. O NUnit, possui uma variedade de atributos que devem ser utilizados em seus testes unitários. Eles são utilizados para definir test fixtures, test methods, setup e teardown. Veremos para que servem cada um deles logo abaixo.

Obs.: A forma mais prática de instalar NUnit no seu sistema é instalando o pacote Test Driven.Net, da versão mais recente do NUnit, instalará um add-in pro Visual Studio.Net para que você possa executar os testes dentro do ambiente, diretamente. Outra alternativa (por exemplo pra quem não usa VS.Net) é instalar apenas o NUnit executá-lo como um programa independente.

TestFixture

O atributo TestFixture é usado para indicar que a classe contém métodos de teste. Quando esse atributo é incluído na classe em seu projeto, o Test Runner irá procurar por métodos de teste para então executá-los. O código abaixo mostra o uso desse atributo.

namespace ExemploNUnitTest
{
  using System;
  using NUnit.Framework;

  [TestFixture]
  public class ClasseDeTeste
  {
  }
}

A restrição para classes que possuem o atributo TestFixture é que deve ter defino um construtor padrão como public.(Ou nenhum construtor definido o que é a mesma coisa).

Test

O atributo Test é utilizado para indicar que o método é um método de teste e deve ser executado pelo Test Runner. O método deve ser público, não deve retornar nada e não pode receber parâmetros. Caso contrário o teste não será executado. O código abaixo mostra o exemplo desse atributo.

namespace ExemploNUnitTest
{
  using System;
  using NUnit.Framework;

  [TestFixture]
  public class ClasseDeTeste
  {
    [Test]
    public void TesteUm()
    {
      // Faz alguma coisa...
    }
  }
}

Setup & TearDown

Muitas vezes é necessário inicializar ou limpar variáveis antes de começar a execução dos testes, poderia fazer isso criando um método privado com essas rotinas e chamá-los em cada método de teste. Mas, para isso existem os atributos SetUp e TearDown que indicam que um método deverá ser executado antes(SetUp) ou depois(TearDown) de cada método de teste. A utilização mais comum desses atributos é quando é necessário criar objetos dependentes (e.g., database connections, etc). Este exemplo mostra o uso desses atributos.

namespace ExemploNUnitTest
{
  using System;
  using NUnit.Framework;

  [TestFixture]
  public class ClasseDeTeste
  {
    private int _valor;

    [SetUp]
    public void Setup()
    {
      _valor = 5;
    }

    [TearDown]
    public void TearDown()
    {
      _valor = 0;
    }

    [Test]
    public void TesteUm()
    {
       // Faz alguma coisa...
    }
  }
}

Assert Class

Além dos atributos, um importante recurso que o NUnit disponibiliza é a classe Assert. Essa classe oferece uma variedade de métodos estáticos que podem ser usados para verificar se o resultado da execução do método está de acordo com o esperado. A seguir veja um exemplo do uso da classe Assert.

namespace ExemploNUnitTest
{
  using System;
  using NUnit.Framework;

  [TestFixture]
  public class ClasseDeTeste
  {
    [Test]
    public void TesteUm()
    {
      int i = 4;
      Assert.AreEquals( 4, i );
    }
  }
}

O resultado do método de teste do código acima seria Verdadeiro, caso o valor de i fosse diferente de 4 o método seria validado como Falso.

Rodando seus Testes

É muito simples rodar seus testes com o NUnit, ele possui duas aplicações diferentes para execução dos testes, a aplicação Windows GUI e a aplicação console XML. Para rodar os testes com a aplicação Windows GUI, basta rodar a aplicação, abrir o assembly onde residem seus testes e clicar no botão "Run". Veja um exemplo da aplicação Windows GUI na imagem abaixo:

A interface do NUnit é bem simples, ela nos mostra todos os testes, com o seguinte código de cores ao lado de cada um: vermelho se o teste não passou, e verde se passou. Uma barra de progresso à direita dá um resultado geral (ou seja, verde só se todos passaram).

Os testes ainda podem ser executados pela aplicação console através de linhas de comando. Ela também gera um XML com os resultados do teste, possibilitando formatá-los com XSLT e CSS para integra-los no Visual Studio. Porém essa aplicação é mais utilizada para Integração Contínua.

Vou terminar esse post por aqui para não ficar muito extenso, com esse conteúdo já da para ir treinando seus testes com o NUnit. No próximo post pretendo ir mais fundo em TDD, mostrar seu exemplo na prática e algumas dicas de metodologia no desenvolvimento.

Dica de Livro: Test-Driven Development by Example;

Até a próxima...


Extension Methods - (Parte 2)

1
Como foi visto no post anterior, os Extension Methods são uma nova feature do .NET Framework 3.5, mas isso não significa que esse recurso pode ser usado somente nessa versão do Framework. Neste post veremos como utilizar os Extension Methods em aplicações com o Framework 2.0.

Para utilizar os Extension Methods em aplicações ou bibliotecas que são compiladas com o Framework 2.0 teremos que criar nosso próprio atributo do tipo System.Runtime.CompilerServices.Extension que é encontrado no assembly System.Core.dll. Ao criar um aplicação para o Framework 2.0 você irá notar que não poderá referenciar o assembly System.Core.dll pois o mesmo só pode ser utilizado para o Framework 3.5.

Então para poder utilizar os Extension Methods em aplicações 2.0, teremos que criar nosso próprio atributo, para criá-lo, basta digitar o seguinte código em seu projeto:
using System;
using System.Text;
using System.Timers;
using System.Runtime.CompilerServices;

namespace ExtensionMethods
{
   static class Program
   {
      // DisplayInterval é um ExtensionMethod que estende a classe Timer
      static void DisplayInterval(this Timer t)
      {
         Console.WriteLine(t.Interval.ToString());
      }

      static void Main(string[] args)
      {
         System.Timers.Timer t = new System.Timers.Timer(2000);
         // Chama o método DisplayInterval normalmente
         Program.DisplayInterval(t);
         // Chama o método DisplayInterval estendido
         t.DisplayInterval();
         // Espera uma ação do usuário
         Console.ReadLine();
      }
   }
}

// Cria o namespace CompilerServices com a classe ExtensionAttibute
// que existe somente em System.Core.dll no .Net Framework 3.5
namespace System.Runtime.CompilerServices
{
   public class ExtensionAttribute : Attribute
   {
   }
}

No código acima criamos o namespace System.Runtime.CompilerServices com a classe ExtensionAttribute que herda da classe Attribute, isso fará com que o código da aplicação imite o código do assembly System.Core. Isto já será o suficiente para usarmos os Extension Methods em uma aplicação com o Framework 2.0. No código acima temos o método DisplayInterval que é um Extension Method que estende a classe System.Timers.Timer.

Como uma das principais caracteristicas dos Extension Methods é estender as funcionalidades das classes sem estar mechendo diretamente no código fonte original nada melhor do que poder usá-lo em aplicações mais antigas, logo, aplicações que ainda usam o Framework 2.0 ao invés do 3.5

Para finalizar, vale resaltar que essa técnica não pode ser aplicada em aplicações ASP.NET que são compiladas no Framework 2.0, pois os Web servers ao efetuarem o deploy não entendem os Extension Methods.

Até a próxima...


Extension Methods - C# 3.0 & Visual Basic 9.0

0
Com o tradicional desenvolvimento orientado a objeto, quando se deseja herdar funcionalidades de uma classe, é necessário criar uma classe que estenda a classe base. Visual Basic e C# suportam esse conceito de orientação a objeto, mas algumas vezes, podemos ter classes marcadas como "NotInheritable" ou "sealed" para previnir que sejam feitas modificações no comportamento da classe através da herança. Como resultado não podemos customizar essas classes. Um exemplo disso é a classe System.String do .Net Framework.

Uma nova feature encontrada no C# 3.0 e Visual Basic 9.0 permite que você estenda as funcionalidades de um tipo existente que não permite utilizar herança. O responsável por isso são os Extension Methods(Extensão de médotos) que tem papel crucial na implementação do LINQ.

Extension Methods provém um mecanismo simples para estender tipos do sistema(valor, referência, tipos interface) com novos métodos. Os métodos estendidos, estendem as funcionalidades de um tipo original, podendo ser chamado normalmente. Eles criam a ilusão de que eles são definidos no tipo real, mas na realidade, não há alteração no tipo original.

Isto não é um conceito padrão de orientação a objeto, isto é uma feature do .Net Framework, que gera um código no Intermediate Language(IL) através do compilador gerando uma chamada compartilhada a um método.

O código abaixo mostra os Extension Methods "AlternateCase" e " IsValidEmailAddress" que estendem a classe System.String que é marcada como "NotInheritable" ou "sealed". O primeiro método retorna uma string com seus caracteres minúsculos e maiúsculos alternadamente. Já o segundo método retorna um valor booleano que resulta da validação de um e-mail.

'VB.NET
Imports System.Runtime.CompilerServices
Imports System.Text.RegularExpressions

Module Module1
   Sub Main()
      Dim mail As String = "test@test.com"
      'Chama método AlternateCase como uma extensão da variável mail
      Console.WriteLine("mail.AlternateCase: " & mail.AlternateCase)
      'Chama o método sem ser uma extensão da variável
      Console.WriteLine("Extensions.AlternateCase(mail): " &
      Extensions.AlternateCase(mail))
      'Chama método IsValidEmailAddress como uma extensão da variável mail
      Console.WriteLine("mail.IsValidEmailAddress(): " & mail.IsValidEmailAddress)
      'Chama o método sem ser uma extensão da variável
      Console.WriteLine("Extensions.IsValidEmailAddress(mail): " &
      Extensions.IsValidEmailAddress(mail))
      'Espera ação do usuário 
      Console.Read()
    End Sub
End Module

Module Extensions 
    <Extension()>_
    Function AlternateCase(ByVal x As String) As String
       Dim b = False
       Dim SB As New System.Text.StringBuilder
       For Each c In x
          If b Then
             SB.Append(Char.ToUpper(c))
          Else
             SB.Append(Char.ToLower(c))
          End If
          b = Not b
       Next
       Return SB.ToString
    End Function

    <Extension()>_
    Function IsValidEmailAddress(ByVal x As String)As Boolean
        Dim regex As Regex = New Regex("^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$")
        Return regex.IsMatch(x)
    End Function
End Module

Note que no Visual Basic é necessário importar o namespace System.RunTime.CompilerServices e utilizar o Annotation "Extension()" em cima do método desejado.

//C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions; 

namespace ExtensionCSharp
{
   class Program
   {
       static void Main(string[] args)
       {
           String mail = "test@test.com";
           //Chama método AlternateCase como uma extensão da variável mail
           Console.WriteLine("mail.AlternateCase(): " + mail.AlternateCase());
           //Chama o método sem ser uma extensão da variável
           Console.WriteLine("Extensions.AlternateCase(mail): " + Extensions.AlternateCase(mail));
           //Chama método IsValidEmailAddress como uma extensão da variável mail 
           Console.WriteLine("mail.IsValidEmailAddress(): " + mail.IsValidEmailAddress());
           //Chama o método sem ser uma extensão da variável
           Console.WriteLine("Extensions.IsValidEmailAddress(mail): " + Extensions.IsValidEmailAddress(mail));
           //Espera ação do usuário 
           Console.Read();
        }
    }

    public static class Extensions
    {
        public static String AlternateCase(this String x)
        {
            Boolean b = false;
            StringBuilder SB = new StringBuilder();
            foreach(Char c in x)
            {
               if (b)
                  SB.Append(Char.ToUpper(c));
               else
                  SB.Append(Char.ToLower(c));
               b = !b;
            }
            return SB.ToString();
         }

         public static bool IsValidEmailAddress(this String x)
         {
             Regex regex = new Regex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");
             return regex.IsMatch(x);
         }
    }
}

Note que em C# também se faz necessário importar o namespace System.RunTime.CompilerServices contido no assemblie System.Core.dll. Porém em C# para identificar o método como um Extension Methods não precisa utilizar Annotations, basta utilizar a palavra reservada "this" na declaração do parâmetro no método, como mostra o código acima.

Após o Extension Method ser criado, ele já fica associado a classe que ele estendeu, neste exemplo a classe System.String foi estendida, e o método pode ser visualizado através do IntelliSense do Visual Studio como mostra a figura abaixo:

Você irá ver que os Extension Methods aparecem com um ícone diferente(flecha azul apontando para baixo) para diferenciá-los dos métodos instanciados pela classe.

As vantagens em usar Extension Methods é que ele permite adicionar funcionalidades para o tipo que você deseja customizar sem que você quebre o código de aplicações já existentes. Você pode estender interfaces padrões com métodos adicionais sem alterar fisicamente a biblioteca de classes existentes.

No próximo post, pretendo mostrar outras vantagens de utilizar Extension Methods e também mostrar um macete para utilizá-lo em projetos com o .Net Framework 2.0

Para baixar a solução completa deste exemplo clique aqui (VS2008).

Até a próxima...


O uso do Reflection

0
Um dos maiores benefícios do .NET Framework e do Common Language Run-Time(CLR) em particular é a riqueza dos tipos de informações em runtime. O System.Reflection permite que você navegue e analise um tipo de dados em tempo de execução. Esta capacidade permite desenvolver sistemas mais dinâmicos e que possuam uma arquitetura modular mais clara.

Entre diversas outras funcionalidades, o Reflection nos pertime encapsular os assemblies. Com isso você pode utilizar o Reflection para acessar um assembly que não esteja diretamente referenciado em seu projeto, podendo obviamente, utilizar suas classes e métodos.

O uso mais comum, útil e prático de Reflection é a chamada de métodos dinâmicos, de um assembly externo ou não.

Veja no exemplo abaixo um código que usa Reflection para carregar a classe HttpUtility do assembly System.Web e usar os métodos HtmlEncode e HtmlDecode em um texto HTML:

'Código em VB.NET:
Imports System.Reflection

Public Class Program
   Public Overloads Shared Sub Main()
      Dim path As String = "C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Web.dll"
      'Instancia o Assembly através do arquivo
      Dim webAssemby As Assembly = Assembly.LoadFile(path)
      'Recebe o tipo de objeto da classe HttpUtility
      Dim utilType As Type = webAssemby.GetType("System.Web.HttpUtility")
      'Recebe os método estáticos HtmlEncode e HtmlDecode
      Dim encode As MethodInfo = utilType.GetMethod("HtmlEncode", _
      New Type() {GetType(System.String)})
      Dim decode As MethodInfo = utilType.GetMethod("HtmlDecode", _
      New Type() {GetType(System.String)})
      'Cria uma string para codificar
      Dim originalString As String = "Isto é um exemplo do uso de Reflection <.>"
      Console.WriteLine(originalString)
      'Codifica e mostra o valor codificado
      Dim encoded As String = CType(encode.Invoke(Nothing, New Object() {originalString}), String)
      Console.WriteLine(encoded)
      'Decodifica para comparar se funcionou corretamente
      Dim decoded As String = CType(decode.Invoke(Nothing, New Object() {encoded}), String)
      Console.WriteLine(decoded)
   End Sub
End Class

//Código em C#:
using System;
using System.Reflection;

namespace ReflectionCSharp
{
   class Program
   {
       static void Main(string[] args)
       {
           String path = @"C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Web.dll";
           //Instancia o Assembly atrav‚s do arquivo
           Assembly webAssemby = Assembly.LoadFile(path);
           //Recebe o tipo de objeto da classe HttpUtility
           Type utilType = webAssemby.GetType("System.Web.HttpUtility");
           //Recebe os m‚todo est ticos HtmlEncode e HtmlDecode
           MethodInfo encode = utilType.GetMethod("HtmlEncode", new Type[] {typeof(System.String)});
           MethodInfo decode = utilType.GetMethod("HtmlDecode", new Type[] {typeof(System.String)});
           //Cria uma string para codificar
           String originalString = "Isto ‚ um exemplo do uso de Reflection <.>";
           Console.WriteLine(originalString);
           //Codifica e mostra o valor codificado
           String encoded = (String)encode.Invoke(null, new Object[] {originalString});
           Console.WriteLine(encoded);
           //Decodifica para comparar se funcionou corretamente
           String decoded = (String)decode.Invoke(null, new Object[] {encoded});
           Console.WriteLine(decoded);
        }
     }
}

É claro que o Reflection não faz apenas isso, possúi vários recursos interessantes, que vale a pena serem explorados. Mas uma coisa é inegável, as chamadas dinâmicas feitas com o Reflection tornam a lógica da sua aplicação bem mais flexíveis.

Até a próxima...

Builder

0
Definição: Separa a criação do objeto complexo da sua representação. O mesmo processo de construção pode criar diferentes representações.

As classes e/ou objetos que participam deste padrão são:

Builder (VeiculoBuilder) o especifica uma abstract interface para a criação das partes do objeto tipo Produto ConcreteBuilder (MotoBuilder, CarroBuilder, CaminhaoBuilder) o constrói e reúne partes do objeto Produto que implementa a interface Builder o define e marca a criação da representação do objeto o fornece uma interface para devolver um objeto do tipo Produto Director (Concessionaria) o constrói um objeto usando a
interface Builder Product (Veiculo) o representa o objeto complexo antes da construção. o incluí as classes que definen as partes que constituem o objeto, incluindo interfaces para reunir as partes no resultado final

Builder: Quando e onde usar?
O padrão Builder é um padrão de projeto de criação de objetos que permite o cliente contruir um objeto complexo especificando somente seu tipo e conteúdo. Os detalhes da construção são escondidos inteiramente do cliente. A motivação mais comum para usar o padrão Builder é para simplificar o code do cliente que cria objetos complexos. O Cliente pode apenas especificar as etapas que são necessárias para o Builder criar o objeto, sem ter conhecimento de como o trabalho atual é finalizado. Um cenário aonde você deve considerar o uso do padrão de projeto Builder é quando é deselvolvido um gerador de código. Você pode escrever uma aplicação que cria stored procedures para diferentes tipos de banco de dados(SQL Server, Oracle, DB2). A saída real é bastante diferente, mas as etapas para a criação dos procedimentos contidos no CRUD(Create, Read, Update, Delete) são todos muitos similares.

O padrão Builder não é amplamente usado, mas pode ser encontrado em duas classes do .NET Framework. VBCodeProvider e CSharpCodeProvider criam classes Builder através do método CreateGenerator. O método CreateGenerator retorna uma interface ICodeGenerator onde a geração do código fonte pode ser controlado.

Para ver um exemplo do design pattern Builder na prática é só baixar o projeto de exemplo que segue no link abaixo:
Exemplo padrão Builder (VS2008)

Até a próxima...

Abstract Factory

1
Definição: Fornece uma interface para criar família de objetos relacionados ou dependentes sem especificar a classe concreta deles.

As classes e/ou objetos que participam deste padrão são: AbstractFactory (ContinenteFactory) o declara uma interface para operações que criam produtos abstratos • ConcreteFactory (AfricaFactory, AmericaFactory) o implementa a operação para criar um objeto produto concreto AbstractProduct (Herbivoro, Carnivoro) o declara uma interface para um objeto do tipo produto • Product (Gnu, Leao, Bufalo, Lobo) o define um objeto produto para ser criado pela fábrica concreta que implementa a interface AbstractProduct • Client (MundoAnimal) o usa as interfaces declaradas pelas classes AbstractFactory e AbstractProduct

Abstract Factory: Quando e onde usar?
O padrão Abstract Factory provém um cliente com uma classe que cria objetos que possuem relação através de características comuns. Outra razão e benefício para criar objetos usando o padrão Abstract Factory é especialmente quando os contrutores das classes são chamadas diretamentes para criar objetos diferentes(overloads). Construtores são limitados no controle de processos de criação de objetos em geral. Se sua aplicação necessita de maior controle, considere usar um Factory. As Factorys incluem cenários que envolvem caching de objetos, sharing, re-using e aplicações que mantenham contadores de objetos e tipos.

Existe momentos em que o cliente não conhece exatamente que tipo deve construir. Isso pode ser resolvido fácilmente com o uso de tipos base ou interface e a fábrica pode passar parâmetros ou outros tipos de informações para tomar essa decisão para o cliente. Um exemplo disso são os provider especificos dos objetos ADO.NET(DbConnection, DbCommand, DbDataAdapter, etc).

Os contrutores não passam muito bem a sua intenção quando existem vários overloads por que eles necessitam ter o mesmo nome da classe (ou Sub New no VB.NET). Construtores com muitos overloads acabam confundindo os desenvolvedores na hora de decidir qual deles usar. Substituir construtores por métodos factory que mostram a sua intenção muitas vezes é o melhor a fazer.

Exemplo:

Vários construtores sobrecarregados, qual deles você deveria usar?

// C# 
public Veiculo (int passageiros);
public Veiculo (int passageiros, int cavalosPotencia);
public Veiculo (int rodas, bool trailer);
public Veiculo (string tipo);

' VB.NET
Public Sub New(ByVal passageiros As Integer)
Public Sub New(ByVal passageiros As Integer, ByVal cavalosPotencia As Integer)
Public Sub New(ByVal rodas As Integer, ByVal trailer As Boolean)
Public Sub New(ByVal tipo As String) 

Com o padrão Factory o código fica mais expressivo e os desenvolvedores mais produtivos:

// C#
public Veiculo CriaCarro(int passageiros);
public Veiculo CriaSport(int passageiros, int cavalosPotencia);
public Veiculo CriaOnibus(int rodas, bool trailer);
public Veiculo CriaBarco();
public Veiculo CriaMoto();

' VB.NET 
Public Function CriaCarro(ByVal passageiros As Integer) As Veiculo
Public Function CriaSport(ByVal passageiros As Integer, ByVal cavalosPotencia As Integer)As Veiculo
Public Function CriaOnibus(ByVal rodas As Integer, ByVal trailer As Boolean)As Veiculo
Public Function CriaBarco()As Veiculo
Public Function CriaMoto()As Veiculo

Para ver um exemplo do padrão AbstractFactory na prática é só baixar o projeto que segue no link abaixo:

http://cid-17621fcd61d3a95a.skydrive.live.com/self.aspx/Blog/AbstractFactory.zip (VS 2008)

Até a próxima...

Bem-vindo aos padrões de projetos

0
O que é padrão de projeto? Para que servem os padrões de projetos?

Essas são perguntas que todo desenvolvedor deveria fazer antes de inicializar um projeto.

Todos nós já usamos bibliotecas e APIs comerciais. Nós a pegamos, escrevemos alguns códigos, compilamos em nossos programas e aproveitamos um monte de códigos que outra pessoa escreveu. Pense nas APIs e em todas as funcionalidades que elas lhe dão: rede, I/O, etc. As bibliotecas e APIs demoram um tempão para chegar a um modelo de desenvolvimento onde podemos apenas selecionar os componentes e utilizá-los. Mas elas não nos ajudam a estruturar nossos próprios aplicativos de maneiras mais flexíveis, faceis de entender e manter. É aí que os Padrões de Projetos ou Design Patterns entram.

Os padrões de projetos não são nada mais que o uso dos princípios de design orientado a objeto, estão num nível acima das bibliotecas. Os padrões de projetos nos dizem como resolver alguns problemas, e é tarefa nossa adaptar esses designs para adequá-los a nosso aplicativo.

"Mas mesmo que eu siga o encapsulamento e conheça abstração, herança e polimorfismo, preciso mesmo conhecer os Padrões de Projetos? Não é muito direto? não é por isso que fiz todos esses cursos sobre Orientação a Objeto?"

Este é um dos mal-entendidos do desenvolvimento orientado a objeto: que conhecendo os princípios de Orientação a Objetos automaticamente seremos bons na construção de sistemas flexíveis, reutilizáveis e de fácil manutenção. Lembre-se, conhecer conceitos como abstração, herança e polimorfismo não faz de você um bom designer orientado a objeto. Um bom design pensa em como criar designs flexíveis que possam lidar com mudanças.

Obs.: Os padrões de projetos não entram diretamente no seu código, primeiro entram em seu CÉREBRO.

Veja mais sobre padrões de projetos em: http://www.dofactory.com/Patterns/Patterns.aspx http://pt.wikipedia.org/wiki/Padr%C3%B5es_de_projeto_de_software E para descontrair: http://desciclo.pedia.ws/wiki/Gambi_Design_Patterns

Até mais, Hugo Estevam