O contato com Scrum, XP e Agile em geral, me levaram à diversos conceitos que até então desconhecia, um dos quais resolvi me aprofundar foi TDD (Test Driven Devolpment), para isso li o livro ”Test-Driven Development by Example” escrito por Kent Beck. O Livro é muito esclarecedor e divertido de ler, apresenta diversos conceitos e técnicas através de exemplos de implementações de pequenas soluções.
TDD ou Desenvolvimento Dirigido por Testes, é uma técnica de desenvolvimento de software que consiste em pequenas iterações onde testes são escritos primeiro e o código produzido é somente o necessário para fazer o teste passar, e finalmente o código é refatorado para acomodar as mudanças. TDD proporciona feedback rápido depois de cada mudança. Não é considerado somente uma técnica de escrita de testes, mas uma técnica para design de software.
TDD pode ser facilmente explicado em cinco simples passos:
- Adicione um teste rapidamente.
- Execute todos os testes e observe o novo teste falhar.
- Faça uma pequena mudança para fazer o teste passar .
- Execute todos os teste e observe que foram bem sucedidos.
- Refatore e remova o código duplicado.
Para melhor entender este ciclo de cinco fases, vamos constuir um pequeno programa Java para realizar um simples cálculo de potência. Para executar os testes precisamos de uma ferramenta de testes unitários, utilizaremos o JUnit.
O JUnit possui uma classes chamada TestCase, a qual nossa classe de teste deverá extender. Essa classe possui uma série de métodos que nos auxiliarão no processo de testes unitários, tais métodos fazem o teste falhar caso determinadas condições não sejam satisfeitas.
Ex: O método “AssertEquals(x,y)“ faz o teste falhar caso x seja direferente de y.
assertEquals(“TDD”,”DDD”); //Falha
assertEquals(100, 100); //Passa
Começaremos então pelo teste:
public class CalculadoraTest extends TestCase {
public void testCalcularOitoAoQuadrado(){
Calculadora calculadora = new Calculadora();
assertEquals(calculadora.calcularPotencia(8, 2), new BigDecimal(“64”));
}
}
Se tentarmos executar nosso teste, o JUnit nos apresentará uma barra vemelha o que signfica que o teste falhou, e falhou porque a classe Calculadora e o método estático calcularPotencia ainda não existem. Vamos então criar a classe e o método para fazer o teste ao menos compilar.
public class Calculadora {
public void calcularPotencia(Integer a, Integer b){
return null;
}
}
Nossa missão agora é fazer, rapidamente, o teste passar. Para isso vamos então retornar o valor que nosso primeiro teste espera, 64.
public class Calculadora {
public void calcularPotencia(Integer a, Integer b){
return new BigDecimal(“64”);
}
}
Se executarmos o teste novamente o mesmo passará… Eu seu, eu sei, que isso pode parecer meio estranho no inicio, mas não se assuste, você se acostumará e entenderá melhor o que está por traz disso tudo com o tempo. Vamos adicionar mais um método de teste em nossas classe de testes
public void testCalcularDoisAoCubo(){
Calculadora calculadora = new Calculadora();
assertEquals(calculadora.calcularPotencia(2, 3), new BigDecimal(“8”));
}
E vamos fazer a alteração mais simples possível para fazer o teste passar:
public class Calculadora {
public void calcularPotencia(Integer a, Integer b){
if (b == 2)
return new BigDecimal(“64”);
else
return new BigDecimal("8");
}
}
Agora que os dois testes estam passando, realizaremos o processo de refatoração para remover as constantes e a redundancia do código, assegurando que o teste continue passando.
public void calcularPotencia(Integer a, Integer b){
BigDecimal result = new BigDecimal(a);
for (int I = 0; I < b; I++) {
result = result.multipliedBy(BigDecimal.valueOf(a));
}
return result;
}
Pronto! Refatoramos nosso código removendo as constantes e se executarmos nossos testes verificaremos que este será bem sucedido! Este é o processo básico de TDD.
Ops!!! O que acontecerá se tentarmos executar o método executar uma potencia de um número elevado a zero? Problemas!!! Todo o número elevado a Zero é igual a 1 (Um), dessa forma devemos adicionar um novo teste que verifique se um número elevado a zero retorna 1, fazer o teste passar, refatorar e assegurar de que os outros testes continuem passando.
public void calcularPotencia(Integer a, Integer b){
if (b.equals(0)){
return BigDecimal.valueOf(1);
}
BigDecimal result = new BigDecimal(a);
for (int I = 0; I < b; I++) {
result = result.multipliedBy(BigDecimal.valueOf(a));
}
return result;
}
public void testTodoNumeroElevadoAZeroDeveRetornarUm(){
Calculadora calculadora = new Calculadora();
assertEquals(calculadora.calcularPortencia(21, 0), new BigDecimal(“1”));
assertEquals(calculadora.calcularPortencia(8, 0), new BigDecimal(“1”));
}
É isso pessoal! Parece simples, mas não é fácil. Depois muito treinamento e prática em diferentes cenários isso começa a ficar mais natural e você passa a enxergar mais claramente os benefício desta poderosa técnica. Estou a disposição para esclarecimento de dúvidas. Comentários são sempre muito bem vindos.
Parabéns pelo artigo André. A leitura é fácil.
Fala André!
Minha primeira visita aqui no seu blog!
Legal que está se aprofundando em TDD… um assunto que ninguem leva a serio!
Bom, para estrear, vou colocar algumas observações, ok?
1) Nos primeiros passos, antes de fazer o teste passar, é necessário fazer o teste falhar. Mas é importante não misturar ‘erro de compilação’ com ‘falha do teste’. O teste só poderá falhar, se, ao menos, o código for compilável. Para faze-lo falhar, basta retornar, por exemplo, BigDecimal.ZERO. O legal de fazer o teste falhar no inicio é que temos certeza de que o que estamos querendo testar está sendo testado (ex.: não estou fazendo ‘import’ de uma outra classe ‘Calculadora’ que já funciona), e, além disso, a ferramenta (ex.: JUnit) não está louca.
2) Logo depois que você fez o método ‘calcularPotencia()’ retornar um ‘new BigDecimal(“64”)’, você decidiu fazer refactoring… ótimo. Porém você acabou avançando demais e trocou a implementação ‘hardcode’ por uma solução melhor, *sem criar uma nova especificação de funcionalidade/behaviour por meio de testes*! É necessário ‘baby steps’, isto é, antes de você retirar o hardcode ’64’, prove através de um outro teste que esta implementação está incompleta! Esse processo é chamado de ‘triangulation’. É é extremamente útil para descobrir (e cobrir) diversos casos de testes. Baby steps, sempre!
3) É interessante separar o exercício do SUT em diversos métodos de testes. Eu costumo quase sempre exercitar apenas uma vez um método do SUT por método de teste. No seu exemplo, eu colocaria o ‘teste com expoente zero’ em um método separado e daria um nome de acordo com o ‘Testcase Class per Class’ (xUnit), tornando explícito a feature (método do SUT) e a fixture… algo como ‘testCalculaPotencia_ComExpoenteZero’. Mais pra frente, se surgir a necessidade, poderia refatorar o teste para usar o ‘TestCase Class per Fixture’, e o nome do método ficaria ‘expoenteZero_deveRetornarUm’.
Espero ter contribuido um pouco com o assunto!
Muito bom Gerson, concordo completamente, seu comentário enriqueceu muito este post… Grande abraço!
Olá, Xará!
Já pratiquei TDD em alguns projetos, porém só recentemente comecei a estudá-lo de verdade.
Você se refere ao “fake it ‘til you make it” neste trecho, certo? -> “Se executarmos o teste novamente o mesmo passará… Eu seu, eu sei, que isso pode parecer meio estranho no inicio, mas não se assuste, você se acostumará e entenderá melhor o que está por traz disso tudo com o tempo.”
Eu até sei uma ou outra razão para se implementar o mínimo necessário só para ir do vermelho para o verde. Mas não sinto que essas razões que eu conheço sejam convincentes para alguém que esteja começando a estudar o assunto. Vou ministrar um curso de TDD em breve, então eu gostaria de ter bons argumentos na manga para justificar isso.
Então, eu lhe pergunto: quais são as razões pelas quais se deve fazer isso?
Valeus!
Olá André,
Você tem um ponto, lembro de um amigo que dizia: “Por que tenho que ver ficar falhar se já sei que vai falhar…” kkkk.
Creio que o importante desses passos todos é utilizar Baby Steps para evitar fazer uma solução mirabolante e perder a SIMPLICIDADE que é um dos princípios básicos do XP. Mas é claro, que você pode ir se liberando se certos rituais quando tiver compreendido esses princípios. Mais ou menos como no Modelo de Dreyfus http://en.wikipedia.org/wiki/Dreyfus_model_of_skill_acquisition.
Abraços.