Primeiramente, perceba que o "descuidado" está entre aspas(" "), com isso quero dizer que ele tem uma razão de existir, e que, óbvio, você não deve negligenciar seus testes.
Me refiro a ser descuidado com "verificações" que fazemos para evitar exceções, como:
- verificar se um objeto é null, para não dar NullPointerException;
- no caso de um Array, verificar se existem elementos antes de tentar pegar a posição 0 ou 1 ou qualquer outra;
- verificar divisões por zero;
- problemas com cast;
- e outros.
Bem, listei acima os que me vieram a cabeça no momento, mas, com certeza, existem muitos outros.
A dica que quero deixar é que abuse dos testes sem fazer "qualquer" dessas verificações, um teste feito com esse "descuido" pode capturar erros que precisariam de várias verificações para serem encontrados. Esses cuidados devem ser tomados no código do seu método (funcionalidade).
Claro que você pode criar métodos que esperam uma exceção, e isso deve ser verificado com maior zelo, mas se este não é o objetivo, e exceções são eventuais, seja "descuidado".
quinta-feira, 20 de dezembro de 2007
quarta-feira, 19 de dezembro de 2007
Como testar um Método Privado?
Pra começar, surgem duas perguntas:
Devo Fazer? Como devo fazer?
A primeira resposta é: Claro que sim, se ele pode quebrar, ele deve por motivo óbvios, e se não pode quebrar, ele deve por motivos estratégicos.
A segunda resposta é: Aí depende. Depende da linguagem, da capacidade do desenvolvedor, do tempo, "beleza" do código, facilidade de manter, etc.
Pra tentar deixar mais claro, vamos considerar duas possibilidades:
- Testar Explicitamente o método privado;
- Testar por intermédio de outro método.
A primeira possibilidade, testar explicitamente, pode-se fazer duas coisas (que me vem a mente, mas podem existir outras):
- Você pode trocar a visibilidade do método para public com Reflection, em tempo de execução;
- Alterar o método para protected. Isso é mais complicado, em casos de código legado ou sistemas de outros.
A segunda possibilidade, a que prefiro, é usar outro método "testável" para testar o método privado.
Na prática, é muito fácil perceber isso, mas vai que você quer entender logo o que estou falando, então vai um exemplo:
Tenho uma classe com um método public (que retorna uma String) e um private, o método private (criptografa a palavra blablabla)
Veja os comentários no código:
ps. Por favor, não reparem o método de criptografar, criei só pra demonstração.
Devo Fazer? Como devo fazer?
A primeira resposta é: Claro que sim, se ele pode quebrar, ele deve por motivo óbvios, e se não pode quebrar, ele deve por motivos estratégicos.
A segunda resposta é: Aí depende. Depende da linguagem, da capacidade do desenvolvedor, do tempo, "beleza" do código, facilidade de manter, etc.
Pra tentar deixar mais claro, vamos considerar duas possibilidades:
- Testar Explicitamente o método privado;
- Testar por intermédio de outro método.
A primeira possibilidade, testar explicitamente, pode-se fazer duas coisas (que me vem a mente, mas podem existir outras):
- Você pode trocar a visibilidade do método para public com Reflection, em tempo de execução;
- Alterar o método para protected. Isso é mais complicado, em casos de código legado ou sistemas de outros.
A segunda possibilidade, a que prefiro, é usar outro método "testável" para testar o método privado.
Na prática, é muito fácil perceber isso, mas vai que você quer entender logo o que estou falando, então vai um exemplo:
Tenho uma classe com um método public (que retorna uma String) e um private, o método private (criptografa a palavra blablabla)
0: public class ClassePeba {O método private (criptografaPalavra) será testado por intermédio do método public getAlgo
1: public String getAlgo(int i)
2: {
3: String palavra = "blablabla";
4: palavra = criptografaPlavra(palavra, i);
5: return palavra;
6: }
7:
8: private String criptografaPalavra(String palavra,
9: int i) {
10: if(i == 1)
11: {
12: palavra = palavra.replace("a", "@");
13: palavra = palavra.replace("b", "!");
14: palavra = palavra.replace("l", "?");
15: }else{
16: palavra = palavra.replace("a", "*");
17: palavra = palavra.replace("b", "$");
18: palavra = palavra.replace("l", "%");
19: }
20: return palavra;
21: }
22: }
Veja os comentários no código:
0: public class TestClassePeba {Acho que fica claro o motivo pelo qual, considero esta abordagem a mais indicada, ela é mais simples, fácil de implementar e de manter, e não a considero nem um pouco deselegante.
1:
2: ClassePeba classePeba;
3:
4: @Before
5: public void prepare()
6: {
7: classePeba = new ClassePeba();
8: }
9:
10: @Test
11: public void testGetAlgo()
12: {
13: //fazendo este teste, o método getAlgo
14: //já está 100% coberto
15: assertEquals("!?@!?@!?@", classePeba.
16: getAlgo(1));
17: //fazendo este teste, o método
18: //criptografaPalavra também fica 100% coberto
19: assertEquals("$%*$%*$%*", classePeba.
20: getAlgo(2));
21: }
22: }
ps. Por favor, não reparem o método de criptografar, criei só pra demonstração.
CruiseControl - Automatizando minha Integração Continua
Ouvi muito se falar de CruiseControl, até mesmo aqui na empresa já estudamos a sua viabilidade. Mas, o que eu ganho com isso?
Uma integração "simples": test, backup, update, test, commit, pode ser executada com um botão ao lado do commit, então não dou "cliques a mais".
Mas lá vem uma vantagem, não preciso esperar a integração acontecer, sendo que ela pode demorar um pouco, aqui na empresa ela dura mais ou menos 5 minutos.
E como ouvi alguem me dizer, "sua maior força é sua maior fraqueza", então: a vantagem descrita acima acarreta no maior defeito, em minha opinião, de Automatizar a minha Integração Contínua, o feedback rápido é perdido.
Eu posso configurar o CruiseControl para me avisar mais rapidamente, posso saber 1 minutos após dar um erro, porém, mas se você estiver fazendo a integração "na mão" e o erro acontecer, você o corrigirá imediatamente.
E caso o gerente receba um email que a integração quebrou, duvido que ele diga, "parem o que esta sendo feito e verifiquem a integração".
Pior que isso, mesmo que o gerente peça para corrigir, é que se duas pessoas jogarem erros que provoquem "quebra" na integração, a pessoa que for corrigi-la pode conhecer apenas um dos erros e a demora para a correção desses erros é maior que seria caso a Integração fosse feita "na mão".
A dificuldade de correção pode acarretar num grande problema, o repositório pode ficar muito tempo quebrado.
O Vinícius da ImproveIT escreveu num post sobre Barrigas Abertas a seguinte frase: "Nos projetos que eu conheci, que utilizavam CruiseControl, era comum o repositório estar quebrado.", isso já me parece motivo suficiente para não usar essa Automatização.
Dois bons links sobre Integração Contínua e CruiseControl:
http://www.improveit.com.br/xp/praticas/integracao
http://weblogs.pontonetpt.com/contracorrente/posts/2008.aspx
Uma integração "simples": test, backup, update, test, commit, pode ser executada com um botão ao lado do commit, então não dou "cliques a mais".
Mas lá vem uma vantagem, não preciso esperar a integração acontecer, sendo que ela pode demorar um pouco, aqui na empresa ela dura mais ou menos 5 minutos.
E como ouvi alguem me dizer, "sua maior força é sua maior fraqueza", então: a vantagem descrita acima acarreta no maior defeito, em minha opinião, de Automatizar a minha Integração Contínua, o feedback rápido é perdido.
Eu posso configurar o CruiseControl para me avisar mais rapidamente, posso saber 1 minutos após dar um erro, porém, mas se você estiver fazendo a integração "na mão" e o erro acontecer, você o corrigirá imediatamente.
E caso o gerente receba um email que a integração quebrou, duvido que ele diga, "parem o que esta sendo feito e verifiquem a integração".
Pior que isso, mesmo que o gerente peça para corrigir, é que se duas pessoas jogarem erros que provoquem "quebra" na integração, a pessoa que for corrigi-la pode conhecer apenas um dos erros e a demora para a correção desses erros é maior que seria caso a Integração fosse feita "na mão".
A dificuldade de correção pode acarretar num grande problema, o repositório pode ficar muito tempo quebrado.
O Vinícius da ImproveIT escreveu num post sobre Barrigas Abertas a seguinte frase: "Nos projetos que eu conheci, que utilizavam CruiseControl, era comum o repositório estar quebrado.", isso já me parece motivo suficiente para não usar essa Automatização.
Dois bons links sobre Integração Contínua e CruiseControl:
http://www.improveit.com.br/xp/praticas/integracao
http://weblogs.pontonetpt.com/contracorrente/posts/2008.aspx
terça-feira, 18 de dezembro de 2007
Livros sobre Testes (em inglês)
Muita gente procura referencias para estudar testes, e numa lista de discussão sobre Java indicaram o site flazx.
Como não me pareceu pirataria, até por ter o link para comprar na Amazon e tal, listei aqui em baixo, alguns livros sobre testes que tratam de assuntos bem interessantes:
http://www.flazx.com/ebook2330.php
http://www.flazx.com/ebook2721.php
http://www.flazx.com/ebook4275.php
http://www.flazx.com/ebook8592.php
http://www.flazx.com/ebook2630.php
http://www.flazx.com/ebook1369.php
Com as férias chegando, esta é uma boa oportunidade para aperfeiçoar, aprender ou conhecer algo mais sobre testes.
* Também é uma boa oportunidade de estudar Inglês.
Valeu a dica, Barroso.
Como não me pareceu pirataria, até por ter o link para comprar na Amazon e tal, listei aqui em baixo, alguns livros sobre testes que tratam de assuntos bem interessantes:
http://www.flazx.com/ebook2330.php
http://www.flazx.com/ebook2721.php
http://www.flazx.com/ebook4275.php
http://www.flazx.com/ebook8592.php
http://www.flazx.com/ebook2630.php
http://www.flazx.com/ebook1369.php
Com as férias chegando, esta é uma boa oportunidade para aperfeiçoar, aprender ou conhecer algo mais sobre testes.
* Também é uma boa oportunidade de estudar Inglês.
Valeu a dica, Barroso.
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:
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.
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: @OverrideNo método de teste, você insere registros para simular um banco de dados populado:
1: protected String[] getConfigLocations()
2: {
3: setAutowireMode(AUTOWIRE_BY_NAME);
4: return new String [] {"applicationContext*.xml"};
5: }
0: public void testGetAvisosMesAno()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.
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: }
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.
sexta-feira, 14 de dezembro de 2007
JMockit - Mockando um Método Estático
É com grande felicidade que consegui, nesta manhã de sexta, mockar um método estático, p&#@ que p@$%&, sensacional.
O nome do cara é JMockit
Bem, o meu exemplo é bem simples:
Criei uma classe que tem somente um método estático, é esta classe que quero mockar:
Redefino os métodos da classe real pelos da classe mock:
E para voltar "ao normal":
No eclipse é só fazer isso, na hora de rodar o teste:

ou isso, para rodar com test suite ou qualquer outro lugar:
O nome do cara é JMockit
Bem, o meu exemplo é bem simples:
Criei uma classe que tem somente um método estático, é esta classe que quero mockar:
0: public class ClasseUtil {Então, uma classe que utiliza este método:
1: public static int metodoEstatico()
2: {
3: return 1;
4: }
5: }
0: public class ClasseFuncionalidade {Por fim, a classe de teste:
1: public static int metodoLocal()
2: {
3: return ClasseUtil.metodoEstatico();
4: }
5: }
0: public class ClasseFuncionalidadeTeste{Perceba que criei uma outra classe (inner class) chamada MockClasseUtil, com um método (metodoEstatico) de mesma assinatura do método da classe ClasseUtil, porém o retorno é diferente (No original é 1 e no falso é 0).
1:
2: public static class MockClasseUtil{
3: public static int metodoEstatico()
4: {
5: return 0;
6: }
7: }
8:
9: @Before
10: public void prepare(){
11: Mockit.redefineMethods(
12: ClasseUtil.class,
13: MockClasseUtil.class);
14: }
15:
16: @After
17: public void finalize(){
18: Mockit.restoreAllOriginalDefinitions();
19: }
20:
21: @Test
22: public void metodoLocal(){
23: assertEquals(0, ClasseFuncionalidade.
24: metodoLocal());
25: }
26: }
Redefino os métodos da classe real pelos da classe mock:
0: @BeforeSe fosse no JUnit3, poderia usar o método setUp para invocar o redefineMethods.
1: public void prepare(){
2: Mockit.redefineMethods(
3: ClasseUtil.class,
4: MockClasseUtil.class);
5: }
E para voltar "ao normal":
0: @AfterA única dificuldade foi "ver" que o comando "-javaagent:jmockit.jar" deve ser passado como parâmetro para a JVM:
1: public void finalize(){
2: Mockit.restoreAllOriginalDefinitions();
3: }
No eclipse é só fazer isso, na hora de rodar o teste:
ou isso, para rodar com test suite ou qualquer outro lugar:
quinta-feira, 13 de dezembro de 2007
JUnit4 - Suite de Testes
Para provar, ainda mais, que o JUnit4 veio "apaixonado" por annotations, lá vai mais uma da série: JUnit4 - Novidades e Melhorias.
Vou mostrar um exemplo de TestSuite nas duas versões, isso tornará a explicação clara:
Para criar uma suite de testes com JUnit3:
Vou mostrar um exemplo de TestSuite nas duas versões, isso tornará a explicação clara:
Para criar uma suite de testes com JUnit3:
0: public class AllTests extends TestSuiteAgora, com JUnit4:
1: {
2: public static TestSuite suite()
3: {
4: TestSuite suite = new TestSuite();
5:
6: suite.addTestSuite(ClasseFuncionalidadeTeste.class);
7: suite.addTestSuite(CopyOfClasseFuncionalidadeTeste.
8: class);
9: }
10: }
0: @RunWith(value=Suite.class)Bem, com JUnit4, a classe "suite" só existe para "receber" a annotation e ser executada, não necessitando de nenhum método.
1: @SuiteClasses(value={
2: ClasseFuncionalidadeTeste.class,
3: CopyOfClasseFuncionalidadeTeste.class
4: }
5: )
6: public class TestAll {
7: }
Assinar:
Postagens (Atom)