Melhorando a expressividade dos testes unitários com JUnitXtension – Parte 1

23.Outubro.2009
Qual dos códigos é mais simples de ler?

Código A

assertFalse(foo.equals(bar));

assertFalse(foobar == 2);

Código B

assertNotEquals(foo, bar);

assertNotEquals(foobar, 2);

Inicialmente criado apenas como um projeto para me auxiliar no trabalho a escrever testes mais simples de ler, resolvi compartilhar no Google Code a biblioteca, atualmente na versão 0.1.1.

A principal motivação foi de criar novas asserções que melhorem a expressividade, mesmo que redundantes, para que o desenvolvedor não tenha que escrever códigos auxiliares limitados à API padrão do JUnit.

Internamente o próprio JUnit trabalha dessa maneira. Praticamente todas as demais asserções são abstrações sobre assertTrue, no máximo adicionando alguma formatação diferenciada para que o resultado e um teste falho seja legível.

Ao invés de, por exemplo, escrevermos assertFalse(0 == 1), podemos escrever assertNotFalse(0, 1). Ao invés de assertTrue(x > 0), podemos simplesmente escrever assertGreaterThanZero(x) e assim por diante.

Outra vantagem diz respeito à cobertura de testes. Em alguns casos, quando se usa assertFalse(x == 0), o algoritmo que calcula a cobertura vai informar que essa linha foi apenas parcialmente testada. Na verdade queremos apenas testar se x é igual a zero, mas se você persegue os 100% de cobertura, esse tipo de problema obriga a criar código extra apenas para manter a cobertura do código satisfatória. Utilizando assertNotEquals(0, x), ou o teste passa ou não passa, reduzindo para apenas duas as opções possíveis, limpando o código e aumentando automagicamente a cobertura.

Se interessou pela idéia? Você encontra os mesmos problemas e quer conhecer mais? A biblioteca está na versão 0.1.1 e pode ser encontrada no Google Code. Críticas construtivas são sempre bem vindas.

Keep testing ;-)

P.S.: Agradecimentos públicos à minha esposa e ao Fábio Serra pelas revisões nesse post


Testes unitários – you’re doing it wrong

14.Maio.2009
Se você:
- Precisa de um método public static void Main(String[] args) para executar seus testes
- Depende de uma ordem pré-determinada para executá-los
- Caso um teste falhe, todos falham na seqüência
- Dependa de um banco de dados, de uma conexão ou de qualquer recurso externo para executar o teste
- Precise rodar os testes na mão toda vez que quiser saber se estão passando
- Escreve testes sem assert com freqüência

Sinto dizer, mas você não está usando testes unitários.

Update: Estou amadurecendo melhor a idéia, mas relendo isso aqui, e conversando com alguns desenvolvedores, fiquei pensando: “OK, você está fazendo errado. Então como é o certo? Por que está errado?”. Fiquei em dívida com vocês. Mas pretendo publicar algo mais decente sobre isso em breve.


Integração Contínua

27.Fevereiro.2009
Instalei em minha máquina de trabalho um servidor de integração contínua.

Quando se trabalha sozinho, num projeto pequeno, isso realmente não faz muito sentido. Porém, trabalhando com um recurso a 500km de distância, e mais dois recursos remotos cuidando de um projeto relacionado, cuja alteração indevida pode quebrar o funcionamento da parte em que trabalho, esse tipo de ferramenta começa a fazer sentido.

O modo correto, antes de mais nada, é instalar esse servidor numa máquina que possa ser facilmente acessada por qualquer parte interessada. Minha máquina de trabalho, obviamente, só fica disponível quando estou conectado na rede da empresa, para quem estiver na mesma condição.

Instalei o Apache Continuum rodando como um NT Service (sim, eu uso Windows no trabalho), acessível como uma aplicação web a partir da porta 8080.

Para quem se interessou, recomendo a leitura do artigo do Martin Fowler sobre Integração Contínua. Estou organizando uma documentação para compartilhar com a equipe e pretendo divulgá-la aqui.

P.S.: Sim, eu sei que tem Bamboo, mas comunicação ainda é um ponto crítico a ser desenvolvido.


Links para hoje

22.Dezembro.2008

Clean Code Talks

09.Dezembro.2008
Miško Hevery, o cara responsável por ensinar os desenvolvedores da Google a manter seus códigos limpos e testáveis e, acidentalmente, meu atual mestre na arte de desenvolver software de modo claro e com qualidade, publicou uma palestra sobre o uso de polimorfismo para substituir IFs e SWITCHs. O que pode parecer nada além do básico de orientação a objetos acaba sendo negligenciado ou esquecido no ambiente real de desenvolvimento.

Espero que seja tão útil para vocês como tem sido para mim.   

Share and enjoy ;-)


Sobre testes, Lisp, certificação e remédios para o estômago

08.Dezembro.2008
Há alguns dias eu publiquei um diálogo imaginário (mas não muito) sobre testes e TDD. Não foi maldade, nem desejo de menosprezar alguém. Há pouco reli um artigo do Shoes chamado Programadores Profissionais Escrevem Testes, Ponto Final e lembrei disso. Leiam, pensem, tentem aplicar alguma coisa.

Sobre o estômago, gostaria de pedir a ajuda de algum leitor com ares de bom samaritano: estou tendo sérios problemas com branch/merge no Subversion. Posso estar ridiculamente enganado, mas estou sentindo falta do velho CVS.

Sobre o elisp (Emacs Lisp), deixei na geladeira. Tentei mais algumas brincadeiras no Clojure, mas algo me diz que vou precisar de um pouco mais de método se quiser realmente aprender a linguagem.

Finalizando, comecei oficialmente a estudar para minha próxima certificação. Para 2009 quero pelo menos mais duas.

;-)


Diálogo imaginário baseado em fatos reais

02.Dezembro.2008
‘Aqui a gente trabalha com TDD’
-’Caramba, que legal. Como funciona aí? Vocês trabalham com User Stories?’
‘Não… a gente trabalha com NUnit mesmo.’
-’Sim, mas o que você usa para criar os testes no NUnit?’
‘Ué, nada. O NUnit cria os testes pra gente. Você não precisa necessariamente escrever código pra testar código.’
-’Ah… então vc não escreve testes?’
‘Claro que não. Tem um pessoal aqui que só escreve testes. Dizem que testes escritos por desenvolvedores são viciados.’
-’Hm… então eles escrevem os testes e vocês codificam em cima disso?’
‘Claro que não. Primeiro a gente programa e entrega, depois eles testam.’
-’Isso não é TDD, é?’
‘Não sei, mas não posso ficar perdendo tempo escrevendo testes.’

Usando Test-driven Development – Semana 1

17.Novembro.2008
Hoje completei uma semana utilizando TDD (Desenvolvimento orientado a testes) em tempo integral. Quando eu digo ‘tempo integral’, entenda como ‘usando do jeito certo’, criando testes primeiro para depois programar a funcionalidade.Com a ajuda de Kent Beck (Test-Driven Development by Example), e dando várias e boas cabeçadas, consegui juntar uma listinha de itens para ajudar os próximos corajosos que resolverem se aventurar nesse modo de desenvolver software.

  • Crie uma história
    Esse é o início de tudo. Provavelmente a história já existe e chega na forma de um requisito. Você pode definir o formato que ela vai ter, ou o projeto já deve ter algum padrão. Nesse projeto eu uso a seguinte estrutura:
    Quando fulano faz tal ação, acontece tal resultado.
    Se você está programando um software de PDV, por exemplo, ela pode ser traduzida como ‘Quando o operador de caixa ler o código de barras, eu devo exibir o nome do produto e o preço‘.
    Nada de extraordinário ou de complicado. Quem já XP alguma vez na vida deve conhecer por User Story.
  • Simplifique
    Provavelmente você não vai conseguir resolver com meia dúzia de linhas de código. Então comece com algo simples, sem perder o foco e sem esquecer do problema que você tem para resolver.
    Usando o exemplo acima, nós logo notamos que o código de barras vem de algum lugar, que as informações sobre o produto estão em outro lugar, e que alguém vai utilizar o resultado disso.
    Com isso, o problema maior se torna três problemas menores e mais simples de resolver.
  • Mantenha uma lista de itens a fazer
    Essa veio direto do livro do Beck, e que eu apanhei por não ter feito. Crie uma listinha, num pedaço de papel mesmo, com os itens que forem aparecendo, as dúvidas que você tiver e os testes que você pretende implementar para aquele problema menor que você resolveu atacar.
    Lembre-se de manter a simplicidade e escrever numa linguagem que você possa lembrar no dia seguinte. No meu caso, tive um fim de semana no meio do desenvolvimento.
  • Um teste por vez
    Outro erro que cometi: criar vários testes de uma vez.
    De repente eu tinha três testes em vermelho, e só podia resolver um por vez. Isso aumenta o nível de stress e dificulta a visualização do problema.
    Nesse caso específico eu tinha um objeto sendo manipulado no método testado, e ao dar return, eu criava uma instância nova e limpa ao invés de devolver aquela que já estava tratada.
    Mais ou menos isso:

    MeuObjeto fazAlgumaCoisa(Valor parametro){
    MeuObjeto retorno = new MeuObjeto();
    retorno.qualquerCoisa(parametro);
    return new MeuObjeto();
    }

    Triste…

  • Um passo por vez
    Criar um teste por vez nos leva a dar pequenos passos, um de cada vez, conforme seu código vai evoluindo. Claro que um pequeno passo para um Martin Fowler pode ser uma maratona para mim.
  • Não se preocupe com desempenho
    Pelo menos num primeiro momento. Otimização sem critérios pode ser uma excelente fonte de bugs aparentemente sem sentido.
  • Seja realista
    Pode apostar: se você nunca usou TDD, você vai perder mais tempo e ficar menos produtivo no início. Todo aprendizado é assim, e com TDD não seria diferente. Então, use o bom senso, se você tem uma tarefa para entregar amanhã cedo, não invente. Não tente usar TDD pela primeira vez na vida em um projeto atrasado e com cronograma estourado. No final das contas você vai ficar com fama de irresponsável, de improdutivo e o TDD vai levar a culpa pelo fracasso do projeto.
    Após o quarto dia eu comecei a sentir a diferença. O código passou a fluir mais rápido, e eu estava confiante de poder prosseguir sem medo de estar ferrando com o resto da aplicação.
    Claro que o fato de você estar criando testes torna o seu código muito mais confiável e menos sujeito a erros, mas eu não recomendo que você considere-o 100% correto e livre de bugs, pelo menos no início. Auto-crítica costuma ser uma virtude nesse caso.
  • E, de novo, simplifique.
    Como dizia um professor que tive, ‘não invente, faça o básico‘. O importante, num primeiro momento, é entregar software funcional. O teste serve, antes de mais nada, para verificar que seu código recebe uma entrada e devolve a saída prevista. Não perca tempo reinventando a roda e tentando parecer sagaz aos olhos de outros programadores. Pensar simples, ao contrário do que parece, é uma das coisas mais difíceis de se aprender.

Essas são as minhas impressões. Nada impede de escrever sobre a segunda semana e dizer algo oposto ao que está em algum ponto daqui. O aprendizado consiste em aprender coisas novas e corrigir falhas antigas.

;-)