Qualidade e JUnit: introduzindo automatização de testes unitários do seu software Java no dia-a-dia
No dia-a-dia do profissional de software, os passos para escrever um código de alta qualidade são: dominar conceitos básicos e boas práticas de programação para deixar o código limpo legível e fácil de ser mantido; se cercar de ferramentas adequadas para avaliação e manutenção dessa qualidade durante e após o desenvolvimento do código; e, entender e dominar princípios de qualidade na aplicação de padrões em seus projetos. E sobre esses assuntos, já discutimos nos três textos que introduziram essa série sobre qualidade:
- Tem um tempinho para conversarmos sobre boas práticas de programação com Java?
- Existe avaliação e garantia de qualidade no seu código durante o desenvolvimento e depois do deploy?
- Princípios S.O.L.I.D: o que são e porque projetos devem utilizá-los
Entretanto, indo além de boas práticas e de uma boa arquitetura, você precisa ter uma estrutura bem organizada para que suas aplicações possam ser devidamente verificadas e testadas.
Code, without tests, is not clean. No matter how elegant it is, no matter how readable and accessible, if it hath not tests, it be unclean. — Uncle Bob Martin
O teste é uma das partes mais importantes para a garantia da qualidade no desenvolvimento de um software. Testar é assegurar que todas as funcionalidades esperadas estão implementadas corretamente. Testar é descobrir falhas/bugs para que sejam corrigidas até que a versão final atinja a qualidade desejada. Testar é validar se uma parte do software está pronta para ser disponibilizada em produção.
Os testes são feitos em várias etapas, sendo a primeira delas o Teste de Unidade. Testes de unidade são testes em um nível de componente ou classe, ou seja, cujo objetivo é um “pedaço do código”. Eles ajudam o desenvolvedor a verificar se a lógica de um programa está correta. A execução de testes ajuda automaticamente a identificar regressões de software introduzidas por alterações no código-fonte. Ter uma alta cobertura de teste do seu código permite que você continue desenvolvendo recursos sem ter que realizar muitos testes manuais.
Tá, mas como automatizar um teste de unidade e facilitar esse processo de garantia da qualidade do seu software Java no seu dia-a-dia? A resposta é: jUnit.
O que é o jUnit?
É um framework open-source para construção de testes automatizados em Java, hospedado no Github, em que se verifica as funcionalidades de classes e seus métodos. Além disso, podemos automatizar também a execução de todos os testes de forma que quando há uma nova versão estável do sistema, o framework execute todos os testes para garantir a integridade e estabilidade do que foi desenvolvido.
O jUnit funciona com base em anotações (Annotations) e essas anotações indicam se um método é de teste ou não, se um método deve ser executado antes da classe e/ou depois da classe. As anotações também indicam se o teste deve ou não ser ignorado e se a classe em questão é uma suíte de teste, ou seja, se a partir desta classe é disparada a execução das demais classes de teste, entre outras anotações menos utilizadas.
Toda classe de teste deve ser criada com a palavra Test no final. Por exemplo, DatabaseConnectionTest. Se você estiver usando uma ferramenta de automação de compilação, por exemplo, o Maven, a regra do sufixo Test para as classes de teste também vale. O Maven (através do plug-in Surfire) inclui automaticamente essas classes em seu escopo de teste. Já um teste é um método contido nessas classes que é usada apenas para teste e anotado com um @Test. Este método é a execução do código em teste.
Testando funcionalidades
Para testar seu código em condições específicas, utilizamos o método assert, fornecido pelo framework ou outra estrutura de declaração, normalmente chamadas de asserts ou assert statements. Essa estrutura permite que especifiquemos uma mensagem de erro e comparar o resultado real do teste com o resultado esperado. Temos vários tipos de asserts que podem ser implementados:
- fail([message]): pode ser usado para verificar se uma determinada parte do código não é atingida ou para ter um teste com falha antes que o código de teste seja implementado. O parâmetro da mensagem é opcional.
- assertTrue([message,] boolean condition): verifica se a condição booleana é verdadeira.
- assertFalse([message,] boolean condition): verifica se a condição booleana é falsa.
- assertEquals([message,] expected, actual): testa se dois valores (esperado e atual) são os mesmos. No caso de arrays, a verificação é em relação à referência e não ao conteúdo.
- assertEquals([message,] expected, actual, tolerance): testa se dois valores float ou double correspondem. A tolerância é o número de casas decimais que devem ser consideradas na comparação.
- assertNull([message,] object): verifica se o objeto é nulo.
- assertNotNull([message,] object): verifica se o objeto não é nulo.
- assertSame([message,] expected, actual): verifica se ambas as variáveis se referem ao mesmo objeto.
- assertNotSame([message,] expected, actual): verifica se ambas as variáveis se referem a objetos diferentes.
Um exemplo de implementação pode ser observado no teste de quantidade de linhas de código da classe HelloWorld.
Nesse exemplo, a quantidade de linhas código dessa classe (sem contar comentários e espaços em branco) é 23. Ao implementar a classe de teste e invocar um método de medição, o assertEquals compara se o valor calculado pela medição é igual ao valor esperado (23).
Para executar o teste e visualizar os resultados, basta clicar com o botão direito do mouse no projeto Java, cuja implementação do teste foi realizada e selecionar as opções Run as -> jUnit Test.
Os resultados dos testes são exibidos na aba JUnit e podem ter 3 tipos de saídas: Errors, quando ocorreu um erro na execução do teste (uma exceção não tratada é lançada ou há erros no código); Failures, quando o teste é executado e falha, seja por um erro na lógica do código ou porque o teste pode estar mal escrito; e, Success, quando o teste passa. No exemplo abaixo, o teste da nossa classe LinesOfCodeTest foi bem-sucedido, ou seja, o valor encontrado na medição do código foi o esperado (23). Também podemos ver exemplos de testes com falhas e outros com erros no código.
Uma outra funcionalidade importante do jUnit é a criação de uma suíte de testes. Uma suíte nada mais é do que uma classe Java onde todos os testes são iniciados, além de descrever quais são as classes de teste do projeto ou padrão dessas classes de teste para a sua execução.
No exemplo abaixo, foi criado uma suíte para gerenciar uma coleção de testes unitários, composta pelos seguintes testes: LinesOfCodeTest, NumberOfMethodsTest e NumberOfAttributesTest. Dessa forma, os testes serão executados na ordem que foram adicionados na suíte.
Como vimos nos tópicos acima, introduzir testes unitários automatizados no seu dia-a-dia é uma tarefa simples e bem trivial. E por que fazer testes unitários? A resposta também é simples. Porque a implementação de testes unitários em seus projetos, permite solucionar dois problemas recorrentes que podem interferir na qualidade de um software: a alta taxa de defeitos, que são os erros frequentes de funcionamento do seu software; e, a deterioração do sistema, que são os problemas a longo prazo que inutilizar o software em um futuro.
Em resumo, só conseguimos descobrir falhas, defeitos e erros se fizermos testes. O teste unitário e a adoção de uma ferramenta muito reconhecida pelo mercado como o jUnit pode tornar esse processo mais rápido e, de certa forma, mais barato. Pense nisso!
Espero que tenham gostado do post. Abraços!
Referências
1. Código Limpo: Habilidades Práticas do Agile Software (Robert C. Martin, 2011)
2. Unit Testing with JUnit: http://www.vogella.com/tutorials/JUnit/article.html