Mostrando postagens com marcador tdd. Mostrar todas as postagens
Mostrando postagens com marcador tdd. Mostrar todas as postagens

quarta-feira, 2 de abril de 2008

Desacoplando para melhorar o design e facilitar os testes

Usando TDD, normalmente nos deparamos com situações em que precisamos mudar nossa implementação para atender a algum teste, isso é normal e viável, já que os testes são parte do sistema.

Quando o Paulo Silveira veio a Fortaleza e ministrou um workshop sobre Java, falou-se muito sobre desacoplar, não usar métodos estáticos (sempre que possível) e outras boas práticas como IoC, acontece que venho buscando sempre soluções pra esses casos sem mudar minha implementação, na maioria até consegui, lógico que com maior dificuldade e esforço.

Assim, esta semana discutimos a implementação de alguns testes que estavam muito complicados e ví que o que o Paulo havia falado era bem simples, ao menos da maneira que solucionamos o problema. Vou ilustrar com um exemplo bem simples pra deixar claro como podemos facilitar os testes desacoplando algumas coisas.

Primeiro quero dizer que com um Spring da vida você não vai precisar usar esse artifício, pois com o IoC do Spring você pode fazer isso;
Segundo, é importante ficar atento, porque se existirem muitas dependência, um artifício simples como esse pode não funcionar.

Ao exemplo:

Tenho 3 classes.
0: public class ClasseA {
1: public void metodo1(){
2: ClasseB classeB = new ClasseB();
3: classeB.metodo2();
4: }
5: }
0: public class ClasseB {
1: public void metodo2(){
2: System.out.println(ClasseUtil.m1());
3:
4: ClasseUtil classeUtil = new ClasseUtil();
5: System.out.println(classeUtil.m2());
6: }
7: }
0: public class ClasseUtil {
1: public static String m1(){
2: return "m1";
3: }
4:
5: public String m2(){
6: return "m2";
7: }
8: }
No teste da ClassseB terei algumas dificuldades porque existe uma chamada a um método estático (podemos usar JMockit, porém, dependendo do retorno, torna-se uma tarefa muito complicada) e um instanciamento da ClasseUtil.

Como poderíamos evitar isso? Bem simples:

Passaremos a instância da ClasseUtil como parâmetro do método:
0: public class ClasseA {
1: public void metodo1(){
2: ClasseB classeB = new ClasseB();
3: classeB.metodo2(new ClasseUtil());
4: }
5: }

0: public class ClasseB {
1: public void metodo2(ClasseUtil classeUtil){
2: System.out.println(classeUtil.m1());
3: System.out.println(classeUtil.m2());
4: }
5: }

O design fica bem simples e os testes mais fáceis de implementar.

segunda-feira, 18 de fevereiro de 2008

Melhorando o Design da Aplicação e a construção de testes

O TDD tem como sua maior incumbência, auxiliar na construção do design dos seus métodos, com o tempo vimos situações particulares e tentamos encontrar soluções para as mais diversas situações, como JMockit, CGLib e outros.

Porém tivemos, aqui na empresa, a visita do Paulo Silveira da CAELUM e mudou bastante o meu conceito de como alcançar a melhoria no desenvolvimento de testes e consequentemente, das classes de um sistema.

Citarei umas idéias que achei bem interessantes:

1) Use Injeção de Dependência. No Spring temos o IoC que nos proporciona essa técnica, é notório a facilidade que ganhamos na construção de testes, principalmente no TDD.
2) Use e abuse de Interfaces. O uso de Interfaces nos oferece, além do óbvio, uma facilidade muito maior na construção de testes, pois o padrão para construção de Mock Objects é utilizar Interfaces.
3) Evite os método estáticos. Métodos estáticos tem alto acoplamento e são ruins de testar, neste momento eu falei sobre JMockit, mas ele frisou que testes devem ser simples e que artifícios como esses podem complicá-los.

Por fim, ele falou algo que achei bem interessante, acho que ele tirou do Design Patters do GoF:

"Evite as extenções e abuse das implementações", não sei bem se foram essas as palavras, mas a idéia foi essa.

E como me lembrou o Capitão Sávio Pierre, "Em vez de extender, procure fazer Agregações ou Composições". Isso também é sensacional.

Bem, acho que existem situações em que essas boas práticas podem não ser possíveis, mas confesso que estarei sempre atento a elas.

Fica aqui a indicação da CAELUM e do próprio Paulo Silveira, o workshop ministrado por ele foi muito bacana.

domingo, 13 de janeiro de 2008

TDD - Os testes devem ser feitos antes ou durante a implementação?

A resposta é clara, DURANTE. Porém, existem vertentes que tratam o "DURANTE" de maneira mais ou menos radical, bem vou tentar explicar o conceito:

A vertente 1 acredita que o código de testes deve ser feito "ao mesmo" tempo que a implementação da funcionalidade. Bem, sejamos mais claros, os praticantes dessa vertente acreditam que deve ir descobrindo o que o método vai fazer a medida que o vai escrevendo.

Nessa abordagem, o FeedBack é mais rápido.

leitura recomendada

A vertente 2 considera a possibilidade de começar "ANTES" e continuar "DURANTE".

Num comentário do Thiago Arrais sobre a leitura recomendada acima, a idéia de fazer o teste "DURANTE" a implementação tem um conceito menos literal, veja a frase: "Escreva os testes que conseguir imaginar antes de começar a escrever o código...".

Como o próprio Thiago se preocupa em "não demorar demais pensando", esta técnica deve ter este cuidado tomado, então concluímos essa abordagem como:

Faça os testes que você idealizou imediatamente, como num validador de PDF, essa técnica diz, por exemplo, que você já pode fazer os testes pra dois CPFs diferentes(um certo e um errado), antes mesmo de implementar a primeira linha do código do validador, você também pode testar um CPF com menos dígitos, com mais dígitos, etc.

Nessa abordagem, a maior vantagem é "pensar mais" antes de começar a implementação.

Eu sou um seguidor da segunda vertente, isso porque considero mais fácil de fazer e obtive melhores resultados fazendo dessa forma, porém não sou nada radical quanto a isso, acredito que existam várias situações em que a primeira vertente seja mais indicada. Também não acho que exista a "forma certa" de fazer, o certo é que vale a pena ser feito.

TDD - Testes são somente a consequência

Pois é, você pode até ter ouvido isso em outro lugar, mas existe uma grande chance de você ter "descoberto" antes mesmo de ouvir.

Sei que é óbvio, mas como vejo muita gente confundindo o "processo" de TDD com Testes Unitários, resolvi dar minha contribuição.

O TDD tem como objetivo dirigir o design da implementação, essa é, sem dúvida, o maior lucro de usar TDD. O segundo maior lucro é ter sua aplicação coberta por testes.

Tendo suas implementações dirigidas por testes, estes se tornam parte da sua aplicação, pra ser mais claro, o código desenvolvido na classe de teste passa a funcionar "até mesmo" como documentação, além do óbvio, que é o fato da sua implementação ter sido baseada no teste. Um método pode, em muitas situações, ter sua implementação modificada pra facilitar o teste.

Por que a confusão(TDD X Testes Unitários) acontece?

Simples, porque fazer TDD é fazer Teste Unitário. Porém, você pode fazer o teste unitário após a funcionalidade estar concluída e isso não é TDD, você terá seu código 100% coberto, mas como já falei, o beneficio maior é dirigir a implementação, ter seu código 100% coberto é apenas a consequência de fazer TDD.

terça-feira, 8 de janeiro de 2008

Testes dirigem o design

Pois é, esses dias tive uma discussão na xprio sobre um teste que estava bem complicado, isso contraria várias idéias de que os testes não devem ser tão complicados de implementar, o que é verdade, eles não devem ser complicados, bem, o caso era o seguinte:

Estava testando um método:
0: public void enviaEmail()
1: {
2: // faz um monte de coisa
3: Mail mail = new Mail();
4: mail.send("Titulo", "Corpo", "email@email.com.br");
5:
6: // faz outro monte de coisa
7: }
Dentro deste método, crio a instância de outra classe(Mail) e invoco o método(send) desta. Aí esta o problema, este método(send) envia emails, e isso é algo que não preciso que meu teste faça.

O Danilo Sato levantou esta bola(o teste dirige o design), e me fez algumas perguntas:

* Que tal passar o e-mail pronto como parâmetro?
* Vale a pena ter o e-mail como atributo dessa classe?
* Qual a responsabilidade dessa classe que está testando? enviar o
e-mail ou criar o email?

Essas perguntas podem ser generalizadas e usadas em situações onde o teste se torne mais complicado.

Como se trata de uma situação onde o código já existe e funciona, fazer uma refatoração é o mais indicado a fazer, nada de grandes manobras, apenas desacoplar algumas funcionalidades ou a criação do objeto, como me indicou o Celestino, com um getInstance por exemplo.

De qualquer forma, fica a lição de que o teste feito quando usamos TDD é normalmente, pra não dizer: sempre, mais simples.

terça-feira, 18 de dezembro de 2007

TDD - Auxiliando a desenvolver consultas com Hibernate + Spring

Quando usamos sql "puro", testar uma consulta é muito simples de ser feito, pois podemos utilizar uma ferramenta de BD como o MySQL Admin, ou PGAdmin, porém quando usamos APIs de Persistência, o problema aparece.

Esses dias, num caso de uso que necessitava de uma consulta nem tão complexa, tive que startar e re-startar o servidor de aplicação 8 vezes, para testar a consulta que era montada.

Daí, fiquei pensando se TDD poderia me ajudar, e ví que ele pode me ajudar e muito.

Usando TDD para o desenvolvimento de uma consulta, além de diminuir o tempo perdido com o servidor de aplicação sendo levantado e derrubado várias vezes, ainda ganho o tempo de abrir a aplicação, preencher campos (como um filtro por exemplo) e submeter um formulário.

Para fazermos TDD com consultas, precisamos gerenciar as transações para garantir que determinadas informações estarão na base de dados e outras não sejam persistidas.

Utilizando Hibernate + Spring precisamos extender a classe AbstractTransactionalSpringContextTests do Spring, para gerenciar as transações.

extends AbstractTransactionalSpringContextTests

No método de testes devemos escrever o método getConfigLocations para carregar os arquivos de contexto do Spring:
0: @Override
1: protected String[] getConfigLocations()
2: {
3: setAutowireMode(AUTOWIRE_BY_NAME);
4: return new String [] {"applicationContext*.xml"};
5: }
No método de teste, você insere registros para simular um banco de dados populado:
 0: public void testGetAvisosMesAno()
1: {
2: Aviso a1 = new Aviso();
3: a1.setId(1L);
4: a1.setData(new Date(107, 11, 1));
5:
6: Aviso a2 = new Aviso();
7: a2.setId(2L);
8: a2.setData(new Date(107, 10, 1));
9:
10: Aviso a3 = new Aviso();
11: a3.setId(3L);
12: a3.setData(new Date(107, 11, 20));
13:
14: avisoDao.save(a1);
15: avisoDao.save(a2);
16: avisoDao.save(a3);
17:
18: Collection avisos = avisoDao.
19: getAvisosMesAno(new Date(107, 11, 1),
20: new Date(107, 11, 30));
21: assertEquals(2, avisos.size());
22: }
Se já existirem dados no banco, o tamanho do retorno (avisos.size()) não é um bom teste, é melhor verificar se os elemento que deseja, estão lá, e se os que não deseja, não estão.


A lição que deve ser deixada é que TDD é tão ou mais útil para um método de persistência do que para um método de negócio.
O "ou mais" é o que acho que se encaixa melhor, pois imagine a possibilidade de errar na criação de uma consulta, além de tudo, usamos muito "palavras" que não são compiladas, como o nome de campos da sua classe.

Outra dica é que mesmo usando SQL puro, o TDD pode conduzir muito bem a construção da sua query, e acho que mesmo sendo fácil testar a consulta com outras ferramentas, o TDD pode trazer mais benefícios do que, simplesmente, testar a consulta.
Veja também o post sobre 100% de Cobertura.

quarta-feira, 5 de dezembro de 2007

TDD - Um exemplo prático

No início, via sempre o exemplo de validação de cpf, as vezes me pegava pensando, será que pra casos "não matemáticos" isso vai funcionar?

Hoje, no trabalho, desenvolvendo um método bem simples, resolvi tirar os "print screen" pra postar aqui e mostrar pros iniciantes em TDD que o assunto em bem próximo da nossa realidade.

Segue o exemplo abaixo:

Dada uma Collection de AtoNormativo* e um objeto do tipo AtoNormativo, façamos um método que retorne a Collection de AtoNormativo excluindo o objeto passado.
*AtoNormativo é uma classe e tem uma propriedade Long id.

1) Crio o método retiraNorma passando uma Collection e um AtoNormativo;




2) Crio o método testRetiraNorma, crio a Collection, adiciono 3 objetos do tipo AtoNormativo e chamo o método retiraNorma, passando um dos objetos inseridos, para ser retirado da Collection.



Perceba que após chamar o método, testo se a Collection foi retornada com 2 elementos somente, se um objeto que não deve ter sido removido, não foi removido, e se um objeto que deve ter sido removido, foi removido.


3) Rodei o teste e deu erro. É o que deve acontecer, já que o método ainda retorna Null.
Implemento o método.




4) Rodo o teste e tudo ok.



A barra verde no canto esquerdo superior da imagem é o sinal de sucesso.

Pronto.


Perceba que a sequência lógica se mantém, penso no que vou fazer, penso no que o método deve fazer, implemento e testo, porém, quando o teste é rodado, você não precisa mais rodar a aplicação, abrir uma determinada tela e executar um certa funcionalidade, o teste garante que, na situação que você pensou, o método irá funcionar.

Perceba que o teste pode ser alterado, podem ser inseridos situações em que o objeto passado não está dentre os objetos da Collection, e isso pode ser feito, o teste não é final, ele sempre pode evoluir.

TDD - Desenvolvimento Orientado a Testes

Quando ouvi a primeira vez sobre Test Driven Development achei imoral, o quão ridículo pode parecer fazer testes antes de desenvolver uma funcionalidade?

Bem, o negócio não é tão feio quanto parece, fazer TDD te ajuda em muita coisa.

Outro dia eu estava fazendo um Caso de Uso relacionado a uma loja virtual, onde deveria retornar vários valores de acordo com os produtos comprados.

Comecei fazendo o Teste:

Primeiro Passo: Gerei a classe de teste e criei somente a assinatura do método.
Segundo Passo: Fiz as simulações necessárias numa calculadora e amarrei no teste, de acordo com a entrada a saída deveria ser... Óbvio que o teste ainda não funcionava.
Terceiro Passo: Parti para a implementação, encontrei menos dificuldades do que imaginava, a lógica estava na minha cabeça, e quando imaginei ter terminado a funcionalidade, simplesmente rodei o teste.

Corrigindo os erros que encontrei no teste e corrigindo o próprio teste, terminei a funcionalidade num tempo muito próximo ao tempo que faríamos sem teste, e o melhor é que a funcionalidade já estava testada, nem precisei abrir a tela pra testar cada situação.

Confesso que existem alguma situações que o TDD não ajuda muito, como por exemplo, num método que somente chama outro método, nesses casos, eu prefiro varrer a classe após a implementação e fazer os testes que faltaram.

Abuse do artifício dos Mock Objects, e experimente o TDD, os seus DEBUGs vão ser menos constantes e você terá a sensação de Casos de Uso concluídos de verdade.

Vocês não imaginam a sensação que tive quando rodei minha classe de testes (haviam 18 simulações testadas) e a barrinha ficou verde.


ps. uso a primeira pessoa (encontrei, tive, Gerei, etc) mas tudo isso foi feito em dupla.

segunda-feira, 3 de dezembro de 2007

O que devo saber pra começar a fazer testes?

Inicialmente, aconselho estudar JUnit, que é uma ferramenta excelente para fazer testes unitários em Java e se integra com o Eclipse, Netbeans e qualquer outra IDE que mereça respeito.

Como os casos de uso evoluem e a complexidade dos testes acompanha, aconselho estudar Mock Objects.

Mocks são simulações de objetos, fazendo com que seus testes fiquem presos a um determinado escopo, por exemplo, se você esta fazendo um teste em uma classe de negócio e o método desta classe invoca um método de outra classe que faz uma consulta no banco, você deve usar um Mock para simular o objeto desta outra classe, simulando assim esta consulta, pois a consulta será testada no momento em que o teste para o seu método for criado.

Com isso você pode começar a fazer testes de seus métodos.

No site da ImproveIT existem vários materiais para estudo no assunto, vou linkar aqui em baixo dois textos bem legais:

Um pouco de TDD, é apresentado um exemplo com JUnit: http://www.improveit.com.br/xp/praticas/tdd

Mock Objects, você pode dar uma lida e ter uma boa idéia sobre o assunto: http://www.improveit.com.br/xp/praticas/tdd/mock_objects