{tecnologia, conceitos, negócios, idéias, práticas, .NET, ruby, osx, ios e algo mais}
23/10/2009
TDD - Test-driven Development - Parte ITDD - Test-driven Development - Parte IIIApós o que foi apresentado no primeiro post da série, faremos nesta segunda parte um exemplo prático de TDD.Para efeito didático este será um exemplo bem simples e trivial, para que possamos focar nas práticas de TDD.Antes de começarmos gostaria de mencionar o "mantra do TDD":
# **"<span style="color: #ff0000;Ou seja, escreva um teste que falhe, na verdade um teste que nem compila (<span style="color: #ff0000;">Red</span>, <span style="color: #008000; ">green</span>, <span style="color: #333399; ">refactoring</span>"**
">vermelho</span>). Escreva um código que compile e faça o teste passar (<span style="color: #008000;
">verde</span>), mesmo que seja um código ruim. Então, após o teste passar melhoramos o código, com a finalidade de deixá-lo mais claro, coeso e simples (<span style="color: #333399;
">refactoring</span>).Tendo isso em mente, vamos para o nosso caso de uso em C#:<blockquote>Desenvolver uma aplicação bancária que controle saques, depósitos e transferências.</blockquote>Como eu disse o caso de uso é simples, para que foquemos no TDD.Vamos criar uma nova solution no Visual Studio. Aqui já começa a mudança de pensamento. Antes de criar o projeto da conta bancária, eu começo criando o projeto de testes da conta bancária. Eu gosto de pensar assim para ir me acostumando com a idéia do Test First.
TDD_criando_projeto_testesAgora que estamos com o projeto criado, vamos escrever nosso primeiro teste. Vocês vão perceber que a prática do TDD nos leva a pensar melhor em nossas classes. Vamos ver.Bom, em nosso primeiro teste vamos nos assegurar de que quando uma conta seja criada ela obrigatóriamente necessite de um depósito inicial:
[TestMethod]
public void Deve_Criar_Conta_Com_Deposito_Inicial(){ ContaBancaria conta = new ContaBancaria(50.0m);
Assert.AreEqual(50, conta.SaldoAtual);
}
Como podemos ver, este teste nem irá compilar, afinal, estamos criando o teste antes mesmo de criarmos a classe ContaBancaria.
public class ContaBancaria{
public decimal SaldoAtual { get;
set;
}
public ContaBancaria(decimal depositoInicial) { }
}
Assi podemos executar nosso teste. Eu gosto de utilizar dois atalhos CTRL + R + T (executa o teste corrente em modo debug) ou CTRL + R + A (executa todos os testes da solution em modo debug).Teremos o resultado como abaixo:TDD - executando primeiro testeEste teste falhou. Ótimo! Obtemos um <span style="color: #ff0000;
">red</span> e sabemos que estamos no caminho certo. Aconteceu que estávamos esperando um saldo de 50 e o saldo obtido foi 0.Agora devemos voltar ao código e fazer o teste passar:
public class ContaBancaria{
public decimal SaldoAtual { get;
set;
}
public ContaBancaria(decimal depositoInicial) { this.SaldoAtual += depositoInicial;
}
}
Neste caso nossa mudança é bem pequena. Agora vamos executar o mesmo teste e ver o que acontece:TDD - executando primeiro teste verdePronto! Obtivemos um <span style="color: #008000;
">verde</span>, e isso quer dizer que podemos prosseguir, escrevendo os próximos testes. Antes disso acontecer, devemos lembrar do próximo passo: refactoring!Vamos voltar ao nosso código e entender o que pode ser refatorado.Me parece que a propriedade SaldoAtual não deveria ter um setter público, desta forma vamos torná-lo privado:
public class ContaBancaria{
public decimal SaldoAtual { get;
private set;
}
public ContaBancaria(decimal depositoInicial) { this.SaldoAtual += depositoInicial;
}
}
Agora devemos novamente executar nossos testes, para termos certeza de que não acabamos estragando nada.Agora precisamos garantir que o depósito inicial seja um depósito válido. Ou seja, o que acontece com um depósito inicial igual a 0? E se for menor que 0?De acordo com a regra de negócio que eu inventei, uma conta sempre deve ser criada com um depósito inicial. Quando quisermos carregar uma conta específica, utilizaremos outra notina e não o construtor público.Assim, vamos criar um deste que garanta que o depósito inicial seja válido.Vamos usar um atributo(attribute) do framework de testes do visual studio que nos permite testar se um determinado teste lança uma exceção - ExpectedException:
[TestMethod][ExpectedException(typeof(DepositoInicialInvalidoException))]
public void Deve_Lancar_Excecao_Deposito_Inicial_Invalido(){ ContaBancaria conta = new ContaBancaria(0);
}
Para que este teste compile, vamos criar uma Exception:
public class DepositoInicialInvalidoException : Exception{
public DepositoInicialInvalidoException() : base("Depósito inicial deve ser um valor maior que 0(Zero)!") { }
}
Agora vamos rodar nosso test. Pumba! Obtivemos um red. Afinal, não estamos validando o depósito inicial. Vamos fazer o código passar:
public ContaBancaria(decimal depositoInicial){
if(depositoInicial <
= 0) throw new DepositoInicialInvalidoException();
this.SaldoAtual += depositoInicial;
}
Agora já podemos executar nossos testes (sim, devemos executar todos os testes, para ter certeza que nada quebrou no meio do caminho). Pronto, temos um verde. Vamos para o refactoring. A validação dentro do construtor não ficou muito elegante. Afinal, no futuro podemos ter outros tipos de validações, desta forma vamos refatorar:TDD - refatorandoAssim o visual studio criará para nós um método, e ficaremos com a seguinte estrutura:
public ContaBancaria(decimal depositoInicial){ Validar(depositoInicial);
this.SaldoAtual += depositoInicial;
}
private void Validar(decimal depositoInicial){
if(depositoInicial <
= 0) throw new DepositoInicialInvalidoException();
}
É importante que a cada refatoring, cada mudança de código, executemos novamente os testes. Mesmo que achemos que a mudança é pouca e pequena, isso nos fará ter o hábito de sempre executar os testes, e no futuro faremos isso para mudanças mais drásticas e severas.Bom pessoal, nesta segunda parte espero ter mostrado o início do TDD utilizando C# + Visual Studio.Na próxima parte deste artigo criaremos os testes para depósitos e saques.
Abraços, Vinicius Quaiato.