Do que se trata o artigo:

Este artigo apresenta um estudo sobre algumas das ferramentas mais usadas para a realização de testes unitários que fazem uso de simulação e validação de interação entre diferentes componentes de um sistema.


Em que situação o tema é útil:

Este artigo pode ser útil ao desenvolvedor que deseja entender como aplicar testes unitários que fazem uso de “mocks”, além de ser uma referência para a escolha da ferramenta mais adequada para atender essa necessidade.

Testes unitários avançados:

Ao aplicar testes unitários, acabamos nos deparando com situações do paradigma da orientação a objetos em que a simples asserção de igualdade promovida por frameworks como o JUnit e TestNG é ineficiente. Nesses casos, pode ser mais interessante validar as interações que ocorrem com as dependências do componente que será testado. Neste contexto, este artigo aborda as diferenças entre alguns dos frameworks mais populares que nos auxiliam a concretizar esse objetivo.

Um dos grandes desafios no que diz respeito ao desenvolvimento de software para uma organização que trabalha com tecnologia é, atualmente, a entrega nos prazos determinados de produtos com qualidade e de baixo custo de investimento. Nesse contexto, os testes unitários, quando bem empregados, auxiliam na otimização da produtividade ao permitir aproveitar mais os códigos pré-existentes, além de facilitar e encorajar o processo de refactoring sem o medo de regressão em componentes inalterados. Em outras palavras, com a aplicação de testes unitários podemos ter mais segurança para garantir que cenários anteriormente testados e aprovados não sejam danificados com a produção de novas fontes.

Em geral, uma classe de teste unitário é uma unidade isolada que tem por responsabilidade validar as entradas e saídas de um determinado componente previamente escolhido. É a menor parte testável de um sistema e é criada e mantida, na grande maioria das vezes, por desenvolvedores. Essas classes possuem o potencial de se tornarem maiores que o próprio código de produção que está sendo testado e, quando criadas a partir de técnicas de TDD, podem nos induzir a crer que estamos programando duas vezes uma mesma funcionalidade.

Test Driven Development ou desenvolvimento orientado por testes é uma técnica que se baseia em três ciclos: primeiro o desenvolvimento de um código que teste uma funcionalidade, em seguida a criação do código de produção que atenda o teste e, por fim, a refatoração do código em padrões aceitáveis.

Os testes unitários são um dos pilares do desenvolvimento ágil introduzido por Kent Beck. De acordo com ele, contribuem para a construção de sistemas mais estáveis e ajudam os programadores a se tornarem mais eficientes reduzindo o número de bugs. Não à toa, esse foi um dos estímulos que o levou, em conjunto com Erich Gamma, a afirmar que “todo programador deveria saber que precisa escrever testes para os próprios códigos”. Indo além, para Bob C. Martin (“Uncle Bob”), “entregar uma linha de código que não tenha sido executada em um teste unitário é uma irresponsabilidade do desenvolvedor de hoje em dia”, porque além das vantagens já expostas, a aplicação dessas estruturas ainda chega a salvar um tempo importante ao promover a redução de horas gastas usando o processo de debug para procurar um problema em específico.

A possibilidade de prevenção de regressão já é por si só uma das maiores atribuições das unidades de verificação. Elas ainda nos permitem alcançar de forma automatizada uma maior cobertura do sistema e, conforme relata Uncle Bob, em determinadas circunstâncias podem tornar mais viável o teste de uma funcionalidade e o comportamento da integração do sistema com uma API de terceiro em relação ao modelo manual tradicional. Ele ainda salienta que é perfeitamente plausível tais testes estimularem uma melhor arquitetura, já que é praticamente inviável escrever testes para códigos com design ruim.

Um teste unitário pode ser entendido como um processo de documentação. De acordo com o autor de “Os três passos para o TDD”, os testes são como a especificação do sistema, e fazendo o uso de pequenos trechos de código, descrevem como o sistema funciona e como deve ser usado. Analogamente, auxiliam no entendimento de como o código se comporta em diferentes cenários, já que quando nos deparamos com um teste legado que acusa uma falha, temos a responsabilidade de entender os cenários nos quais ele deveria funcionar para só então poder corrigi-lo. Dessa maneira, sabemos um pouco mais sobre o sistema e isso gera confiança na utilização desse recurso, afinal quando o comportamento esperado e coberto por testes é desviado, os testes logo vão denunciar.

Segundo Martin Fowler, a necessidade de automatizar a realização de testes regulares de maneira a evitar a regressão, e a vontade de torná-los mais rápidos e fáceis para que os engenheiros de software passassem a executá-los com maior frequência, resultaram na criação das primeiras ferramentas de testes unitários. Ainda na concepção dele, foram essas ferramentas as responsáveis pela popularização de metodologias de desenvolvimento ágil como eXtreme Programming e Test Driven Development. No entanto, a democratização das ferramentas de testes é atribuída a Kent Beck e Erich Gamma, quando construíram o JUnit.

O JUnit é o resultado da idealização de Kent Beck para a automação de testes e é considerada a ferramenta precursora da realização de testes unitários como conhecemos hoje. A partir dela surgiram ideias inovadoras como a barra de progressos que indica a quantidade de testes realizados e a proposta de resultado visual usando a cor verde para indicar o resultado de um teste que atende um comportamento esperado e vermelho para um teste que, por sua vez, não atende. O JUnit ainda é o responsável por inspirar o nascimento do TestNG, um concorrente direto, e de ferramentas com o mesmo propósito, mas especializadas em outras linguagens de programação, como o CppUnit para C++ e o DUnit para Delphi.

A principal funcionalidade de uma ferramenta projetada para a realização de um teste unitário é a validação dos resultados de saída a partir de valores de entrada conhecidos. Essa característica é observada tanto no JUnit quanto no TestNG a partir de classes e métodos de asserções usando, por exemplo, os métodos Assert.assertTrue() e Assesrt.assertEquals(). No entanto, embora existam casos nos quais a realização de um teste unitário possa ser limitada a essa forma de verificação, na grande maioria dos casos lidamos com a necessidade de abranger a interação entre diferentes componentes únicos e, é nesse momento que nossos testes básicos passam a lidar com a necessidade do uso de representações, os chamados “mocks”, para as dependências da classe que está sendo testada.

O EasyMock, o jMock e o Mockito estão entre as mais populares ferramentas que auxiliam o processo de verificação da comunicação com as dependências de uma classe que está sendo testada. Usando processos específicos para a geração de mocks e mecanismos distintos para a simulação do funcionamento deles, elas são capazes de validar comportamentos esperados da unidade que está sendo testada e, assim, ajudam a projetar e testar as informações compartilhadas entre esses objetos. Entretanto, apesar de construídos para um mesmo fim, acabam atingindo-o de maneiras diferentes, e são essas características que os tornam mais aptos ou inaptos para diferentes necessidades. Neste artigo exploraremos, de forma prática, algumas das diferenças entre essas três opções para a realização de tarefas do cotidiano da criação de testes unitários com a utilização de classes “mockadas” em conjunto com o JUnit.

Testes unitários usando Maven

A maneira mais simples de definir as dependências necessárias de bibliotecas de uma aplicação é a partir da utilização do Maven. Não por outro motivo é que a primeira etapa para o uso das anotações e métodos aplicados neste artigo dependerá da configuração do arquivo pom.xml conforme descreve a Listagem 1. Nessa configuração é importante observar que todas as bibliotecas estão anotadas com o escopo de teste porque não é interessante entregá-las em conjunto com o código de produção e que a dependência jmock-legacy só é necessária quando o framework escolhido for o jMock e houver o interesse de se criar mocks de classes já implementadas.

...
Quer ler esse conteúdo completo? Tenha acesso completo