Professional Documents
Culture Documents
simples e prtico
Veja nesse artigo uma forma simples e prtica o
Desenvolvimento Orientado a Testes (TDD) e suas vantagens
em relao aos mtodos de desenvolvimento tradicionais.
0
Gostei (3)
(0)
Fala desenvolvedores!!!
Hoje falarei um pouco sobre TDD, mas sem aprofundar no tema, apenas para plantar
a sementinha da idia. Este o primeiro de uma srie de artigos que tero uma
anlise mais profunda onde resolveremos alguns problemas com TDD.
Definio simples
TDD Alexandre? O que ? TDD o Desenvolvimento Orientado por Testes (Test
Driven Development). Isso mesmo! Desenvolvemos o nosso software baseado em
testes que so escritos antes do nosso cdigo de produo!
Se voc nunca ouviu sobre TDD, ou j ouviu mas nunca tentou, sugiro ferozmente
que voc continue lendo o artigo e procure sobre o assunto! A idia do TDD j
antiga e foi firmada com o mestre Kent Beck (Autor tambm do famoso livro sobre
TDD, que recomendo) e um dos pilares (l-se prticas) do Extreme Programming!
Basicamente o TDD se baseia em pequenos ciclos de repeties, onde para cada
funcionalidade do sistema um teste criado antes. Este novo teste criado inicialmente
falha, j que ainda no temos a implementao da funcionalidade em questo e, em
seguida, implementamos a funcionalidade para fazer o teste passar! Simples assim!
Ciclo de desenvolvimento
Red, Green, Refactor. Ou seja:
Cdigo mais limpo, j que escrevemos cdigos simples para o teste passar
Cdigo da aplicao mais flexvel, j que para escrever testes temos que
separar em pequenos "pedaos" o nosso cdigo, para que sejam testveis, ou
seja, nosso cdigo estar menos acoplado.
Mito
Muitos ainda no gostam da idia do TDD pelo fato de termos mais cdigo a ser
desenvolvido, acarretando maior tempo no desenvolvimento de uma funcionalidade.
Mas isto est errado. Com toda certeza voc desenvolvedor j corrigiu um bug no
sistema, mas criou outros dois no lugar. Isto acontece com muita frequncia e muitas
das empresas ainda pagam os desenvolvedores somente para corrigirem bugs e at
reescreverem sistemas cuja manuteno terrvel, traumtica e sangrenta!
Reforando os motivos
A mdio prazo (e dependendo do sistema a curto prazo) este tempo de
desenvolvimento com TDD menor que o tempo de manuteno corrigindo bugs e
mesmo para adicionar funcionalidades novas. Isto devido, resumidamente, a:
Novo Teste
Este primeiro passo o pilar do TDD (no brinca!). Temos uma nova funcionalidade
do sistema e fazemos o processo inverso ao tradicional: Testamos e Codificamos e
no Codificamos e Testamos.
No primeiro momento isto parece estranho, esquisito ou feio, mas no . O fato de
termos um teste primeiro que o cdigo garante que daremos passos simples para a
codificao da funcionalidade, afim de fazer o teste passar, ou seja, seremos
"obrigados" a escrever uma implementao simples para que o teste passe.
No comeo esta forma no muito intuitiva e o grfico de aprendizagem no l um
dos melhores, mas com o tempo e aperfeioamento da tcnica, esta ser a forma
mais intuitiva e segura de desenvolver que voc encontrar.
Teste Falhando
Neste momento, acabamos de escrever o teste e no temos a implementao. bvio
que o teste falhar, pois ele espera uma resposta que ainda no temos implementada
em lugar algum. Com um Teste falhando na nossa frente, temos um nico objetivo na
vida: Faz-lo passar! Passamos para a prxima fase:
Nova funcionalidade
J ouviu falar no KISS? "Keep It Simple, Stupid", ou seja, devemos escrever o nosso
cdigo da forma mais simples possvel. Cdigo limpo, simples e funcional! Esta a
idia.
Assim, neste momento vamos esquecer as Boas prticas, a Inverso de Controle, os
Patterns, etc e vamos codificar a nossa nova funcionalidade da forma mais simples
possvel para fazer o nosso Teste passar. Neste momento estamos simplesmente
escrevendo alguma funcionalidade que faa o teste passar (sem quebrar outros
testes) e tambm teremos segurana na Refatorao deste mesmo cdigo daqui a
alguns minutos.
Vale lembrar tambm daquela sequncia tima de desenvolvimento que devemos ter
na cabea: Cdigo que funciona -> Cdigo simples e limpo -> Cdigo rpido
Agora com a nova funcionalidade implementada e o teste passando, seguimos para a
prxima fase:
Refatorao
Agora sim! Voc purista da programao que condenou a minha gerao por eu ter
falado para abandonarmos as boas prticas de desenvolvimento, agora sim pode ficar
tranquilo!
Neste momento que vamos analisar melhor aquele cdigo que fizemos
simplesmente para o nosso Teste passar. neste momento que retiramos duplicidade,
renomeamos variveis, extramos mtodos, extramos Classes, extramos Interfaces,
usamos algum padro conhecido, etc. neste momento que podemos deixar o nosso
cdigo simples e claro e o melhor de tudo: Funcional!
Temos um teste agora que indicar qualquer passo errado que podemos dar ao
melhorar o nosso cdigo. E no somente este cdigo que acabamos de escrever. Aps
algum tempo com TDD, ser criada uma Suite de Testes, onde poderemos refatorar
Ambiente
Para o nosso "sistema" vou utilizar Java+Eclipse+JUnit. Imagino que voc j
conhea um pouco de Java e Eclipse, portanto no mostrarei a instalao deles pois
fugiremos do escopo do artigo. Caso tenham algum problema, s entrar em
contato.
Alexandre, posso fazer em C#? A vontade! Pode usar C#+NUnit. Posso usar Delphi? A
vontade! Pode usar Delphi+DUnit!
Primeiro exemplo
Este ser um exemplo bem trivial. No nosso "sistema" precisaremos criar uma
Calculadora que calcula as 4 operaes bsica: Adio, Subtrao, Multiplicao e
Diviso. Simples assim!
Passos para a criao do projeto:
Com a estrutura bsica criada, agora vamos criar a nossa primeira classe. A classe
Calculadora Alexandre? No! A classe CalculadoraTeste? Isso mesmo. Vamos fazer
um Teste em algo que ainda no temos implementado.
Assim, criamos a classe CalculadoraTeste no pacote criado:
package artigotdd.calculadora.teste;
public class CalculadoraTeste {
}
Tudo certinho por aqui. Agora devemos pensar sobre o nosso problema. Queremos
fazer algumas operaes nesta calculadora e para comearmos pensaremos em uma
soma. Como podemos testar uma soma?
Simples e trivial: Dados dois valores, o resultado deveria ser a soma deles.
Vamos ento escrever exatamente este Teste! Vamos criar um mtodo que indique
este teste. Para o JUnit entender que o mtodo "testvel", temos a anotao
"@Test" no mtodo (No esquea do import do JUnit).
Assim temos:
public class CalculadoraTeste {
public class CalculadoraTeste {
@Test
public void deveriaSomarDoisValoresPassados() throws Exception {
}
}
Isso mesmo. O nome do nosso mtodo deve mostrar exatamente o que ele est
querendo fazer. comum encontrar mtodos de teste comeando com "deveria" e
ingls voc tambm vai encontrar o "should".
Agora que temos o mtodo de teste, vamos indicar pra ele o que queremos. Vamos
agora inserir duas variveis e usar o mtodo "assertEquals" do prprio JUnit.
Como o prprio nome diz, o "assertEquals" indica que estamos querendo afirmar algo.
(No esquea do import: "import static org.junit.Assert.*").
Vamos ao cdigo:
public class CalculadoraTeste {
@Test
public void deveriaSomarDoisValoresPassados() throws Exception {
int valorA = 1;
int valorB = 2;
int soma = 0;
assertEquals(3, soma);
}
}
Pronto! Queremos o resultado 3 para a soma das variveis valorA e valorB. Acabamos
de escrever o Teste e bvio que ele no passa. Vamos rod-lo? Boto direito na classe
de teste -> Run As -> JUnit Test.
Barra vermelha, era o que temamos!
Este cdigo nem mesmo compila! Sendo bem ortodoxo em relao ao TDD, realmente
este o nosso prximo passo. No criamos a classe pra depois us-la e sim usamos a
classe pra depois cri-la.
Agora que o compilador gentilmente com uma linha vermelha e um "x" vermelho nos
avisou do erro, basta criarmos a classe Calculadora. Vamos dar um passo um
timo! Se rodarmos o nosso Teste, vamos ver a barra vermelha novamente. Isso por
que criamos o mtodo mas ele no implementa o que precisamos. Vamos agora
implementar:
public class Calculadora {
public int soma(int valorA, int valorB) {
return valorA + valorB;
}
}
@Test
public void deveriaSubtrairDoisValoresPassados() throws Exception {
Calculadora calculadora = new Calculadora();
int valorA = 1;
int valorB = 2;
int soma = calculadora.subtrai(valorA, valorB);
assertEquals(3, soma);
}
Teste da diviso
A funcionalidade simples: Fazer a diviso de dois nmeros. Lembrando: um caso
simples e isolado onde a inteno voc imaginar um caso real da sua aplicao.
Ento vamos para o Teste!
Mas aqui comearamos com aquele conceito de BabySteps, onde faramos passos
curtos para chegarmos soluo, correto? Correto, porm os BabySteps no so uma
regra xiita que devemos seguir risca. Segundo o prprio Kent Beck em seu livro
sobre TDD, os BabySteps so para quando realmente no temos confiana suficiente
em escrever determinado cdigo. Como ele cita tambm, no devemos desenvolver
com BabySteps a todo momento e sim devemos ficar felizes por podermos faz-lo
quando desejarmos.
Agora que lembramos disso, vamos correr um pouquinho mais no cdigo e escrever
um pouco mais rpido que no artigo anterior, porm sinta-se vontade para colocar a
sua velocidade:
Adicionando o mtodo de Teste nossa classe de CalculadoraTeste:
public class CalculadoraTeste {
@Test
public void deveriaDividirDoisValoresPassados() throws Exception {
int valorA = 6;
int valorB = 2;
Calculadora calculadora = new Calculadora();
int divisao = calculadora.divide(valorA, valorB);
assertEquals(3, divisao);
}
}
Calculadora {
Legal! Agora podemos rodar o nosso Teste e v-lo passando. Uma observao
simples: No comentei nos artigos anteriores mas s para ter certeza que de
conhecimento de todos, vou comentar: Rodamos os nossos Testes clicando com o
boto direito na classe de Teste, selecionando Run As e em seguida selecionando
o JUnit Test.
Agora temos um Teste verde na nossa frente!
CalculadoraTeste {
@Test
public void deveriaDividirDoisValoresPassados() throws Exception {
int valorA = 6;
int valorB = 0;
O que fizemos: atribumos o valor zero varivel valorB. E o que esperamos no nosso
assertEquals? No tenho noo! Podemos esperar tudo, menos um valor! Sendo
assim, na sua aplicao, voc poderia mostrar uma mensagem para o usurio
solicitando gentilmente que ele insira um valor coerente. E como podemos fazer um
Teste esperando uma exceo? Vamos l!
public class
CalculadoraTeste {
@Test
public void deveriaDividirDoisValoresPassados() throws Exception {
int valorA = 6;
int valorB = 3;
Calculadora calculadora = new Calculadora();
int divisao = calculadora.divide(valorA, valorB);
assertEquals(2, divisao);
}
@Test
public void deveriaLancarUmaExcecaoIndicandoFalhaAoDividirUmNumeroPorZero()
throws Exception {
int valorA = 6;
int valorB = 0;
Calculadora calculadora = new Calculadora();
int divisao = calculadora.divide(valorA, valorB);
assertEquals(0, divisao);
}
}
@Test(expected = ArithmeticException.class)
public void deveriaLancarUmaExcecaoIndicandoFalhaAoDividirUmNumeroPorZero()
throws Exception {
int valorA = 6;
int valorB = 0;
Calculadora calculadora = new Calculadora();
calculadora.divide(valorA, valorB);
}
}
Agora podemos avanar um pouco mais! Comeamos o artigo com uma nova palavra:
"Mock".
Definio simples: Um Mock basicamente um objeto falso, que capaz de simular
as dependncias de um objeto e capaz de simular determinadas aes desse objeto.
Por que usado? Para testar o comportamento de outros objetos desejados.
Por que gostaramos de testar o comportamento de outros objetos? Justamente para
termos certeza de que tudo ocorreu conforme pensamos. Vamos imaginar a seguinte
situao: Temos uma aplicao onde cada vez que exclumos uma pessoa, um log
gerado no banco no banco de dados com o nome da pessoa que foi excluda.
Como poderamos ter certeza que a gerao do log realmente vai ser
chamada e que nada de ruim acontecer no caminho?
Podemos fazer este teste usando exatamente um Mock da classe de Log. Vamos fazer
o cdigo mais simples que vier na nossa cabea:
//Classe do nosso Teste
public class PessoaTeste {
@Test
public void deveriaCriarUmLogQuandoUmaPessoaForExcluida()
throws Exception {
Pessoa pessoa = new Pessoa();
pessoa.setNome("Alexandre");
PessoaController pessoaController = new PessoaController();
pessoaController.exclui(pessoa);
// Como saberemos se realmente o "criaLog" ser chamado?
}
}
//Nosso Controller
public class PessoaController {
private PessoaDAO pessoaDAO;
private Log log;
public PessoaController() {
pessoaDAO = new PessoaDAO();
log = new Log();
}
public void exclui(Pessoa pessoa) {
PessoaDAO.exclui(pessoa);
log.criaLog(pessoa.getNome());
}
}
//Nossa classe de criao de Logs
public class Log {
public void criaLog(String nomeDaPessoa) {
// Cdigo para criar um Log no banco, em um txt, etc...
}
}
}
}
Mas temos um detalhe: O nosso Controller est com uma dependncia forte que a
classe Log sendo instanciada diretamente pelo Controller. Isso impossibilita o uso do
nosso Mock. Ento vamos melhorar um pouquinho o Design da nossa aplicao. Opa!
Olha o TDD nos "obrigando" a melhorar o Design da nossa aplicao!
Vamos ento aplicar um princpio bem importante que a Inverso de
Controle atravs da Injeo de nossas Dependncias. Vamos enviar ento o
nosso GeradorDeLog para o Controller atravs do construtor.
Assim teremos:
//Nossa classe de Teste
public class PessoaTeste {
@Test
public void deveriaCriarUmLogQuandoUmaPessoaForExcluida()
throws Exception {
Pessoa pessoa = new Pessoa();
pessoa.setNome("Alexandre");
LogMock nossoLogMock = new LogMock();
PessoaController pessoaController = new PessoaController(nossoLogMock);
pessoaController.exclui(pessoa);
assertEquals(pessoa.getNome(), nossoLogMock.getNome());
}
}
//Nosso Controller
public class PessoaController {
private PessoaDAO pessoaDAO;
private GeradorDeLog log;
public PessoaController(GeradorDeLog log) {
this.pessoaDAO = new PessoaDAO();
this.log = log;
}
public void exclui(Pessoa pessoa) {
PessoaDAO.exclui(pessoa);
log.criaLog(pessoa.getNome());
}
}
Recapitulando
Muitas vezes precisamos testar o comportamento dos nossos objetos. No nosso caso
qual o comportamento? A criao de um Log quando uma pessoa excluda. Mas
no queremos criar um Log de verdade quando fizermos o teste de excluso e sim
queremos verificar se o mtodo da criao do Log foi chamado.
Para fazermos isso, usamos um objeto "Mockado" que um objeto que simula o
comportamento do nosso objeto. , a grosso modo, um objeto falso que no tem
inteligncia.
Assim, pelo Teste, na excluso de uma pessoa um Log gerado pois ao chamar o
mtodo de excluso, o mtodo de criao do Log tambm chamado, ou seja, nada
de errado acontece pelo caminho.
Para uma viso geral do nosso Teste, vamos listar os nossos passos:
Sentimos dificuldade para fazer o teste no Controller pois ele estava muito
acoplado com a classe de Log;
Passamos para o nosso Controller a nossa classe de Log "Mockada", por Injeo
de Dependncia pelo construtor;
Finalizando
Legal! Conseguimos fazer o nosso Teste rodar, melhoramos um pouco o Design da
nossa Aplicao aplicando a Inverso de Controle (mas podemos refatorar para algo
bem melhor, claro!) mas acabamos ficando com essa classe horrorosa que
a LogMock.
Mas esta idia no s feia! Essa idia s poder ser usada caso tenhamos objetos
simples. Imagine que temos um objeto que instancia outro objeto e este tambm
instancia outro objeto e cada um tem diversos mtodos. A nossa vida se resumiria a
criar classes de Mock e isso no legal.
neste ponto que podemos usar frameworks para isso. Podemos "Mockar" as nossas
dependncias atravs destes Frameworks sem precisar criar outras classes para isso!
O desenvolvedor de hoje realmente tem que dominar a tcnica que, apesar de
parecer nova, desde os primrdios da civilizao Inca! O seu software funciona?
Sim? Mas no tem testes? Ento voc no tem garantia alguma que ele funciona!
Alexandre Gama
Computao e Matemtica na USP, com breve passagem pela Poli(USP) e pelo BCC(USP). Empreendedor e lder tcnico
na AGR Comunicao Digital e scio e lder tcnico na Digiminds Group. Desenvolveu diversos projetos em Java, Delphi
[...]