Testes unitários
Conteúdo |
INTRODUÇÃO
O processo de desenvolvimento do PJE 2 é baseado no Test Driven Development (TDD) ou em português Desenvolvimento guiado por testes. Este modelo inicia o desenvolvimento de funcionalidades a partir da criação de testes para o código a ser construído. Para tanto o desenvolvedor deve ter o domínio da fucionalidade a ser construída: ele deve conhecer seus parâmetros de entrada e as saídas esperadas, incluindo eventuais exceções. Este documento descreve um exemplo simples de desenvolvimento de uma nova funcionalidade solicitada por um usuário baseado no modelo TDD.
INFRAESTRUTURA NECESSÁRIA
Para realização dos testes unitários deve ser utilizado o JUnit 4.0 ou superior. Para utilização do framework basta configurar a dependência no arquivo pom.xml do projeto, conforme figura abaixo, caso não esteja configurado.
Recomenta-se também a utilização do FEST Fluent Assertions para as asserções. Este framework apresenta asserções mais fluentes e flexíveis para verificação dos retornos dos métodos testados. Para utilizá-lo basta configurar a dependência no arquivo pom.xml do projeto, conforme figura abaixo.
Para criação dos casos de teste com o Eclipse pode-se também utilizar os plugins MoreUnit Eclipse Plugin e Generate Test Case Eclipse Plugin.
REQUISITO DO USUÁRIO
O usuário solicitou que a equipe de desenvolvimento criasse uma calculadora para as 4 operações matemáticas fundamentais: soma, subtração, multiplicação e divisão. A calculadora deve trabalhar sempre com dois números reais, realizando a operação sobre eles e devolvendo um resultado real.
Solução proposta
Para resolver o problema a equipe de desenvolvimento modelou a solução conforme ilustração do diagrama abaixo.
A equipe de desenvolvimento concluiu pela criação de uma classe abstrata (OperacaoFundamental) que define a interface de uma operação que recebe dois elementos do tipo double e retorna uma resultado também double. Esta definição de interface é essencial para que o desenvolvedor conheça os as características do problema a ser resolvido.
Projeto dos testes unitários
Com base no TDD, antes de iniciar a construção da solução o desenvolvedor deve dominar (ou ter acesso a quem domine) o comportamento esperado das operações que devem ser implementadas. Isso é importante para que possam ser desenhados os testes antes da construção da funcionalidade que deve ser testada. Com este conhecimento podem ser construídos casos de teste para cada uma das operações. Para a funcionalidade solicitada o desenvolvedor projetou os casos de teste descritos na figura abaixo.
Conforme apresentado na imagem, o desenvolvedor conhece os valores que fazem cada uma das operações executarem com sucesso e com falha. Com base nisso poder ser construídos os testes unitários.
Criação dos testes unitários
Para criação dos testes unitários no PJE 2 deve ser usado o JUnit. Na estrutura de pacotes do desenvolvimento do CNJ os testes unitários devem ser criados sob o diretório src/test/java, dentro de um pacote que tem o mesmo nome do pacote onde consta o código alvo dos testes, conforme destacado na imagem abaixo.
Dentro do pacote ilustrado pela figura (br.jus.cnj.pje.operacoes) devem ser criadas as classes de teste. Cada classe de negócio deve ter sua respectiva classe de teste. A classe de teste deve ter o mesmo nome da classe que alvo do teste seguido do sufixo Test. Para as classes definidas no diagrama acima teremos as seguintes classes de teste:
- classe Soma: SomaTest
- classe Subtracao: SubtracaoTest
- classe Multiplicacao: MultiplicacaoTest
- classe Divisao: DivisaoTest
Para criar as classes de teste basta utilizar o atalho Ctrl+N sobre o pacote br.jus.cnj.pje.operacoes, e escolher a opção JUnit Test Case.
Classe SomaTest
De acordo com os cenários de teste descritos na figura acima, a classe SomaTest terá 2 métodos de teste, um para cada conjunto de parâmetros. A seguir é apresentado o código da classe, responsavel por testar a classe Soma.
Observe que para cada entrada na tabela (à direita) de valores dos cenários há um método anotado com @Test. Estes métodos anotados são os responsáveis por testar os métodos da classe alvo. Com a classe de teste criada pode-se então criar a classe Soma, responsável por uma das operações da calculadora solicitada pelo usuário. Abaixo é apresentada a figura com a implementação da classe Soma.
Classe DivisaoTest
De acordo com os cenários de teste descritos, a classe DivisaoTest terá 3 métodos de teste, um para cada conjunto de parâmetros. A seguir é apresentado o código da classe, responsavel por testar a classe Divisao.
Com a classe de teste criada pode-se então criar a classe Divisao, responsável por uma das operações da calculadora solicitada pelo usuário. Abaixo é apresentada a figura com a implementação da classe Divisao.
Importante observar que a classe DivisaoTest possui um método a mais - divisao3() - que testa as situações em que se sabe da existência de alguma exceção com base no conjunto de valores do método. Neste caso, como a exceção é prevista e "tratada", o resultado do teste é bem sucedido. Neste exemplo é utilizado o recurso de rules do JUnit, que é uma forma de tratar situações de exceção, entre outras funcionalidades providas pelo recurso.
A criação das classes de negócio e de teste para as operações de subtração e multiplicação segue o mesmo procedimento, definindo-se inicialmente as classes de teste e, posteriormente, as classes de negócio.
MOCK DE OBJETOS
No exemplo apresentado anteriormente todas as classes testadas estavam completamente implementadas, e não dependiam de outras classes para realizar suas funcionidades. Entretanto, este cenário é bastante incomum, visto que são esperadas inúmeras dependências (legítimas ou não) entre as diversas classes/interfaces de um sistema corporativo. Em situações como esta é frequente que classes a serem testadas possuam dependências de classes/interfaces ainda não implementadas. Além disso, pode não ser viável a utilização dos objetos reais de classes já implementadas, por questões de velocidade dos testes, por exemplo, visto que é um princípio dos testes unitários a velocidade de sua execução. Por fim, o próprio processo de desenvolvimento pode conduzir ao uso exclusivo de objetos mockados em vez de objetos reais, como é o caso do Behavior Driven Development (BDD). [1].
Neste contexto é comum o uso de objetos "mockados", ou seja, objetos que são dublês de objetos reais, cuja funcionalidade é somente fornecer dados para serem usados em testes unitários de outra classe. Diversas soluções podem ser utilizadas para esta finalidade: Mockito, EasyMock, Powermock, JMock entre outros. Os exemplos apresentados neste documento é baseado no Mockito, entretanto, o desenvolvedor pode utilizar o framework que melhor atender às suas necessidades ou que melhor conhecer.
Infraestrutura necessária
Para configurar o Mockito basta que a intenção de uso esteja declarada nas dependências do arquivo pom.xml do projeto. Provavelmente o projeto da aplicação já terá esta dependência declarada no arquivo, mas caso não exista, basta declará-la conforme ilustrado pela figura abaixo.
As dependências de outros frameworks podem ser encontradas nos respectivos repositórios: JMock, Easy Mock e Powermock. O desenvolvedor pode ficar a vontade para escolher o framework que desejar.
Exemplo com Mockito
O exemplo deste documento trata de uma consulta de processo que recebe com parâmetro uma interface de processo (IProcesso) e uma interface de pessoa (IPessoa). O alvo do teste é a funcionalidade de consulta do processo, sendo que as interfaces ainda não estão implementas ou, por alguma motivo, não se deseja utilizar as implementações. Trata-se apenas de um exemplo com fins didáticos sem qualquer relação com eventual consulta real de processos presentes nos sistemas do CNJ. O código ilustrado pela figura seguinte apresenta a definição das interfaces IProcesso e IPessoa.
Importante ressaltar que não há classes que implementam as interfaces ilustradas na imagem, contudo, mesmo que houvesse tais classes, é possível realizar os testes unitários sem conhecer a implementação delas. Apesar disso, no entanto, é obrigatório o conhecimento do comportamento esperado dos métodos das interfaces. A seguir é apresentado o código da classe ConsultaProcesso, responsável por realizar a consulta a partir dos parâmetros passados.
Em uma implementação real este método provavelmente retornaria com frequência mais de um processo, dependendo dos parâmetros passados. A implementação exemplificada, contudo, retorna sempre um único processo na lista (o mesmo passado no parâmetro). O importante desta implementação são os comandos das linhas 14 e 15, em que há a chamada dos métodos getNumero() da interface IProcesso e getNome() da interface IPessoa. Como já comentado, estes métodos não estão implementados por qualquer classe. Isso significa que a chamada realizada nas linhas não retornaria qualquer valor. A figura seguinte apresenta a classe de teste criada para testar a classe ConsultaProcesso.
A seguir é apresentada uma explicação das linhas de importância da classe de teste:
- linhas 21 e 24: a anotação @Mock, proveniente do Mockito, indica que a classe de teste não se interessa pela implementação dos atributos pessoa e processo. Este desinteresse pode ser pelo fato de as interfaces não estarem implementadas ou simplesmente porque o desenvolvedor não deseja utilizar a implementação.
- linhas 29 a 33: o método init() faz o setup da classe de teste, preparando os dados para a execução do teste.
- linha 31: a chamada do método initMocks() faz a inicialização dos atributos anotados com @Mock.
- linha 37: o comando desta linha tem a seguinte semântica: quando for invocado o método getNome() do atributo pessoa o retorno deve ser igual ao valor passado como parâmetro em thenReturn(). Neste caso o valor do parâmetro está decladado no atributo estático na linha 18.
- linha 38: tem semântica igual à da linha anterior: quando for invocado o método getNumero() do atributo processo o retorno deve ser igual ao valor passado como parâmetro em thenReturn(). Neste caso o valor do parâmetro está decladado no atributo estático na linha 19.
- linha 40: contém a chamada do método alvo do teste, com a passagem dos objetos mockados como parâmetros. O retorno será uma lista com apenas um processo, conforme já mencionado.
- linha 42: sabendo que o retorno da pesquisa é sempre e apenas um processo, esta linha armazena este processo em uma variável local.
- linha 44: verificação do JUnit se a lista "p" contém apenas um processo.
- linhas 45 e 46: verifica se o processo retornado possui os atributos número e nome da pessoa conforme esperado.
- linhas 48 e 49: conhecendo a implementação do método consultaProceso() (e neste caso é obrigatório o conhecimento da implementação do método a ser testado), sabe-se que os métodos getNome() da interface IPessoa e getNumero() da interface IProcesso são invocados uma e apenas uma vez no método. Estas duas linhas fazem a verificação se estes métodos foram realmente invocados apenas uma vez. Este tipo de verificação dá maior segurança à implementação, pois pode haver métodos cuja quantidade de invocações pode ser relevante para os resultados do método testado.
REFERÊNCIAS
JUnit: http://junit.org
Test Driven Development: http://pt.wikipedia.org/wiki/Test_Driven_Development
FEST Fluent Assertions (documentação): https://github.com/alexruiz/fest-assert-2.x/wiki
FEST Fluent Assertions (dependências): http://mvnrepository.com/artifact/org.easytesting/fest-assert-core
MoreUnit Eclipse Plugin: http://moreunit.sourceforge.net/#overview
Generate Test Case Eclipse Plugin: https://wiki.openmrs.org/display/docs/Generate+Test+Case+Plugin#GenerateTestCasePlugin-Thingstodo
JUnit Theories: https://github.com/junit-team/junit/wiki/Theories
Mock de objetos: http://martinfowler.com/articles/mocksArentStubs.html
Behavior Driven Development (BDD): http://dannorth.net/introducing-bdd/
Mockito: http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html
Mais sobre Mockito: http://blog.caelum.com.br/facilitando-seus-testes-de-unidade-no-java-um-pouco-de-mockito/
JMock: http://www.jmock.org/
Powermock: https://code.google.com/p/powermock/
Easy Mock: http://easymock.org/