De que se trata o artigo: Uso do framework EasyMock para teste unitário de software Java, utilizando objetos mock. Neste artigo foi realizado o teste unitário de um servlet, sem precisar executar a aplicação web, utilizando objetos mock através do framework EasyMock para simular a requisição.

Fornecer um meio para construir casos de teste utilizando objetos mock de forma ágil, permitindo que partes críticas da aplicação possam ser testadas de forma automatizada. Mostrar uma solução de teste para casos naturalmente difíceis de serem testados como, por exemplo, partes de código que dependem de outras partes que ainda não estão prontas.

Em testes unitários de software Java para melhoria da qualidade do produto de software final, permitindo que partes críticas possam ser testadas desde o início do desenvolvimento.

Os testes unitários são essenciais para garantir que menores unidades do software sejam testadas, mas essas unidades podem depender de outras partes do código que ainda não estão prontas. Outra situação refere-se à propagação do erro, onde é importante conseguir isolar uma determinada classe a ser testada, independente daquelas que são chamadas por ela, eliminando-se dúvidas sobre a origem do erro. Uma solução para esses casos é apresentada através da utilização de objetos mock, com o framework EasyMock. Neste artigo simulamos uma requisição web utilizando um objeto mock nos casos de teste para testar um método de um Servlet.

Testes com Objetos Mock

Validação, Verificação e Teste

Todo processo de software deve envolver em algum momento a fase de testes. O ideal seria que simplesmente todo o código pudesse ser testado exaustivamente para garantir que um software sem nenhum defeito fosse entregue ao cliente. Mas sabemos que mesmo com uma aplicação pequena, um teste completo, executado de forma exaustiva, seria inviável. Então, a forma que os analistas de teste, e outros profissionais que tenham que desempenhar este papel no processo de software, encontram para identificar os defeitos no software é concentrar os testes nas áreas mais críticas, como partes que serão mais utilizadas pelo usuário e partes que contenham um processamento mais complexo. Existem vários tipos de testes que podem ser utilizados de acordo com a necessidade.

Os testes unitários são essenciais para que seja possível testar a menor unidade do software como um método, uma classe ou mesmo um objeto. Mas essas unidades a serem testadas, principalmente as mais complexas, podem depender de outras partes do código que não queremos testar no momento, por que não estão prontas ou por que podem comprometer os resultados do teste gerando dúvidas sobre qual é a origem do erro. Uma solução para estes casos é a utilização de objetos mock.

Este artigo mostra um exemplo passo a passo da implementação de um teste unitário que utiliza objetos mock.

Objetos Mock e o Framework EasyMock

Os objetos mock são objetos “falsos” que simulam o comportamento de uma classe ou objeto “real” para que possamos focar o teste na unidade a ser testada. Os objetos mock podem ser extremamente úteis nas situações acima citadas onde é necessário criar um caso de teste e existem dependências entre os objetos. Como mostraremos a seguir, sua utilização é bastante simples e agiliza bastante o processo de construção de testes unitários.

Antes dos objetos mock, uma alternativa eram os stubs, objetos criados para substituir aqueles que seriam chamados numa troca de mensagem. Estes tipos de objeto, por um lado, facilitavam os testes, mas ao mesmo tempo podiam demandar geração de muito código extra, já que em alguns casos era necessário gerar cópias das classes reais para realizar os testes. Os objetos mock não são stubs, mas poderíamos dizer que são tipos de stubs que requerem muito menos código. A principal exigência na utilização de objetos mock é a implementação de interfaces, que já são utilizadas em várias situações, por serem uma boa prática de programação orientada a objetos, e a sua inclusão no modelo de classes não requer muitas alterações.

Para criarmos os objetos mock utilizaremos o framework EasyMock, gratuito e de código aberto, que está disponível neste link.

Basta realizar o download, extrair os arquivos e adicionar os arquivos com extensão “.jar” ao projeto para começar a criar os objetos mock. É necessário também ter uma versão atualizada do JUnit instalada, uma vez que o EasyMock utiliza-se deste outro framework.

Estudo de Caso

Tomemos como exemplo uma aplicação web composta por uma página com um formulário de dados de alunos que chama um servlet ao clique de um botão. A página chama o servlet passando a requisição contendo os dados do formulário. O método que recebe esta requisição é o processRequest() do servlet, mostrado na Listagem 1.

Listagem 1. Implementação do método processRequest do Servlet

        protected void processRequest(HttpServletRequest request, HttpServletResponse response)

        throws ServletException, IOException {

        response.setContentType("text/html;charset=UTF-8");

        String mensagem = "";

        if (verificarAprovacao(request)){

        mensagem = "Aluno foi Aprovado!";

        }else{

        mensagem = "Aluno foi Reprovado!";

        }

        request.setAttribute("mensagem",mensagem);

        getServletContext().getRequestDispatcher("/index.jsp").forward(request,response);

        }
        

O método processRequest() do servlet chama um outro método do servlet, verificarAprovacao() (ver Listagem 2), passando o objeto request que foi enviado pela página web. O objeto request contém os parâmetros com os dados que foram inseridos no formulário da página.

Listagem 2. Implementação do método verificarAprovação do Servlet

        public boolean verificarAprovacao(HttpServletRequest request) {

        String vBuffer_notafinal = "";

        String nome = request.getParameter("vNome");

        float nota1 = Float.parseFloat(request.getParameter("vNota1"));

        float nota2 = Float.parseFloat(request.getParameter("vNota2"));

        vBuffer_notafinal = request.getParameter("vNotaFinal");

        int frequencia = Integer.parseInt(request.getParameter("vFrequencia"));

        float notafinal;

        if (vBuffer_notafinal == ""){

        notafinal = 0;

        }else{

        notafinal = Float.parseFloat(vBuffer_notafinal);

        }

        Aluno objAluno = new Aluno(nome, nota1, nota2, notafinal, frequencia);

        return objAluno.calcularAprovacao();

        }
        

O método verificarAprovacao() é o alvo dos nossos casos de teste. Este método recebe o objeto request e obtém, a partir dele, os dados inseridos no formulário através da chamada ao getParameter(), passando o nome do respectivo parâmetro do formulário. Então é criado o objeto aluno chamado “objAluno”, passando a freqüência e as notas informadas para o construtor do objeto e é feita uma chamada ao método calcularAprovacao() da classe Aluno (ver Listagem 3) que retorna true se o aluno foi aprovado ou false caso não tenha sido aprovado.

Listagem 3. Classe Aluno

        public class Aluno {

        private String nome;

        private float nota1;

        private float nota2;

        private float notaFinal;

        private int frequencia;



        public Aluno(String nome, float nota1, float nota2, float notaFinal, int frequencia){

        this.nome = nome;

        this.nota1 = nota1;

        this.nota2 = nota2;

        this.notaFinal = notaFinal;

        this.frequencia = frequencia;

        }



        public boolean calcularAprovacao() {

        float media;

        if (this.frequencia < 75) { return false; } else { media=(this.nota1 + this.nota2) / 2; if (media < 30) { return
            false; } else { if (media>= 70) {

            return true;

            } else {

            if (((media + this.notaFinal) / 2) >= 50) {

            return true;

            } else {

            return false;

            }

            }

            }

            }

            }
            

Teste Unitário utilizando Objetos Mock

O nosso objetivo é implementar os casos de testes para o método verificarAprovacao() do servlet. Mas para isso, seria necessário passar uma requisição HttpServletRequest no momento do teste, como o request passado pela página web na aplicação. Para isso, num primeiro momento, seria necessário executar a aplicação a partir do browser, uma vez que este objeto é passado pela página web para o servlet (Figura 1). Entretanto, para a construção de testes automatizados para o servlet, será necessário substituir o objeto criado pelo browser por um objeto mock.

Interação entre os objetos envolvidos, sendo o formulário web, o servlet e classe Aluno
Figura 1. Interação entre os objetos envolvidos, sendo o formulário web, o servlet e classe Aluno

O objetivo é realizar testes no método verificarAprovacao() do servlet e, para isso, podemos criar uma requisição mock. Será criada uma requisição falsa simulando uma requisição do tipo HttpServletRequest que é, na verdade, uma Interface, o que facilita a criação do objeto mock a partir dela.

Durante os testes, o formulário que envia os dados para o servlet não será executado em nenhum momento e o browser sequer será aberto. O objeto mock que será criado enviará a requisição diretamente para o servlet como se fosse a requisição do formulário da aplicação Web, conforme esquema da Figura 2.

Representação do objeto mock no teste da aplicação
Figura 2. Representação do objeto mock no teste da aplicação

Para quem conhece o JUnit, a estrutura do teste é semelhante e, para quem está começando nos testes unitários, não tem grande mistério. Será criada uma classe de teste que herda da classe TestCase do JUnit. Esta classe será chamada de TestesAprovacao.

É necessário incluir um import estático na biblioteca do EasyMock para criar os objetos mock, como mostrado na Listagem 4. Lembrando que os arquivos do framework EasyMock devem ter sido adicionados ao projeto anteriormente.

Listagem 4. Criação da classe de teste

        import junit.framework.TestCase;

        import static org.easymock.EasyMock.*;



        public class TestesAprovacao extends TestCase{



        }
        

Casos de Teste

Para identificar quais casos de testes serão necessários para cobrir o método verificarAprovacao(), pode-se calcular sua Complexidade Ciclomática (CC) utilizando alguma ferramenta de métricas, ou então pode-se descobrir quais são as possibilidades de retorno de valor para este método, de acordo com os dados informados. Observando atentamente o método calcularAprovacao() da classe Aluno, verifica-se que se tem cinco possibilidades de retorno:

  1. Freqüência inferior a 75, a função retorna false;
  2. Freqüência igual ou superior a 75 e média inferior a 30, a função retorna false;
  3. Freqüência igual ou superior a 75 e média igual ou superior a 70, a função retorna true;
  4. Freqüência igual ou superior a 75 e média final igual ou superior a 50, retorna true;
  5. Freqüência igual ou superior a 75 e média final inferior a 50, a função retorna false.

Com base nessas informações, conclui-se que, para este exemplo, tem-se cinco possibilidades de retorno da função verificarAprovacao() e serão implementados casos de teste para cada uma destas possibilidades.

Reprovação por Freqüência Insuficiente

O primeiro caso de teste que será criado é o que testa a reprovação por freqüência insuficiente. Para isso, a Listagem 5 apresenta o método, na classe de testes TestesAprovacao, chamado testAlunoReprovadoInfrequencia().

Listagem 5. Implementação do Caso de Teste testAlunoReprovadoInfrequencia()

        public void testAlunoReprovadoInfrequencia() {

        HttpServletRequest requestMock = createMock(HttpServletRequest.class);

        expect(requestMock.getParameter("vNome")).andReturn("Marco");

        expect(requestMock.getParameter("vNota1")).andReturn("0");

        expect(requestMock.getParameter("vNota2")).andReturn("0");

        expect(requestMock.getParameter("vNotaFinal")).andReturn("0");

        expect(requestMock.getParameter("vFrequencia")).andReturn("74");

        replay(requestMock);

        ServletControllerWeb servletControllerWeb = new ServletControllerWeb();

        assertFalse(servletControllerWeb.verificarAprovacao(requestMock));

        }
        

O método inicia com a criação do objeto requestMock. A sua criação é bem simples, a única diferença da criação de um objeto comum está no fato de que, ao invés de chamar o construtor, chama-se o método createMock() passando como parâmetro a Inteface HttpServletRequest, ou seja, de onde será criado o objeto falso.

Entretanto, o objeto falso, como não foi instanciado a partir da classe original, não sabe como responder às chamadas de métodos. Então, as linhas seguintes, através do método expect, instruem o objeto mock como ele deve se comportar quando forem feitas requisições a ele, retornando o especificado no método andReturn(). Trata-se do treinamento do objeto mock e deve-se fazer isso por que estamos utilizando um objeto falso, que não veio de uma página web. Este treinamento termina com o método replay, estando o mock pronto para ser utilizado no teste. Desta forma, o mock foi instruído para retornar valores específicos para os métodos que deve responder. Deve-se perceber que é necessário fazer o treinamento para cada método chamado contendo a assinatura completa do mesmo, incluindo os parâmetros, caso existam.

Para que se possa então testar a reprovação por freqüência insuficiente, precisa-se apenas retornar um valor inferior a 75, no caso foi utilizado o valor imediatamente inferior.

Finalmente, nas duas últimas linhas estão respectivamente a criação do servlet servletControllerWeb e a execução do teste com a chamada ao verificarAprovação() deste servlet, passando o objeto mock que foi criado no teste. Como na reprovação por freqüência insuficiente sabe-se que o verificarAprovacao() deve retornar false, utilizou-se a função assertFalse() para fazer esta verificação. Ao clicar com o botão direito na classe de teste e executá-la, no NetBeans o resultado deve ser algo como exibido na Figura 3.

Resultado da execução de testAlunoReprovadoInfrequencia(
Figura 3. Resultado da execução de testAlunoReprovadoInfrequencia()

O framework EasyMock permite ainda a criação de objetos mock que não retornem uma exceção caso ele não tenha sido treinado para uma determinada situação prevista na sua classe original. Para visualizar esta situação, pode-se comentar a primeira linha de treinamento do mock que contém o comando expect, fazendo o treinamento para o retorno do parâmetro que contém o nome do aluno.

Ao executar novamente o conjunto de testes, gera-se uma exceção por não estar preparado para responder quando é feita a requisição ao objeto com o parâmetro vNome (Figura 4).

Resultado da execução de testAlunoReprovadoInfrequencia() com o teste modificado
Figura 4. Resultado da execução de testAlunoReprovadoInfrequencia() com o teste modificado

Substituindo o construtor do mock de createMock para createNiceMock, pode-se perceber que o teste voltará a passar, mesmo com a linha comentada do treinamento para o parâmetro nome. Claro que este teste só pode ser feito uma vez que o parâmetro nome não era mais necessário no restante do teste.

Reprovação por Nota

O segundo caso de testes é bem semelhante ao primeiro. Será criado um novo método na classe de testes TestesAprovacao chamado testAlunoReprovadoNota(). Sua implementação é apresentada na Listagem 6.

Listagem 6. Implementação do Caso de Teste testAlunoReprovadoNota()

        public void testAlunoReprovadoNota() {

        HttpServletRequest requestMock = createMock(HttpServletRequest.class);

        expect(requestMock.getParameter("vNome")).andReturn("Marco");

        expect(requestMock.getParameter("vNota1")).andReturn("30");

        expect(requestMock.getParameter("vNota2")).andReturn("29");

        expect(requestMock.getParameter("vNotaFinal")).andReturn("0");

        expect(requestMock.getParameter("vFrequencia")).andReturn("75");

        replay(requestMock);

        ServletControllerWeb servletControllerWeb = new ServletControllerWeb();

        assertFalse(servletControllerWeb.verificarAprovacao(requestMock));

        }
        

Agora será necessário passar os parâmetros vNota1 e vNota2. O método deve retornar false se a freqüência for maior ou igual a 75 e a média dos parâmetros Nota1 e Nota2 for inferior a 30. Para a nota 2, está sendo passado o valor “29”, para testarmos o valor limite. A utilização de testes exercitando os valores limite das condições é muito importante, pois, caso contrário, os testes podem estar retornando um resultado não verdadeiro. Para exemplificar isso, basta substituir no código fonte um sinal de “<” por um de “<=”, situação não muito difícil de ser confundida quando da implementação de um algoritmo. Executando os casos de teste novamente, tem-se o resultado dos dois casos de teste implementados até agora.

Aprovação por Nota

A terceira situação refere-se à aprovação de alunos por nota. Assim, será criado mais um método na classe TestesAprovacao chamado testAlunoAprovadoNota(). A diferença deste para o anterior está nos parâmetros vNota1 e vNota2, que agora devem retornar média igual ou superior a 70 e, no retorno do método verificarAprovacao() que agora deverá ser verdadeiro, sendo verificado pelo comando assertTrue(). Serão considerados os valores “70” para ambas as notas, para testar o valor limite. O código deste caso de teste é apresentado na Listagem 7.

Listagem 7. Implementação do Caso de Teste testAlunoAprovadoNota()

        public void testAlunoAprovadoNota() {

        HttpServletRequest requestMock = createMock(HttpServletRequest.class);

        expect(requestMock.getParameter("vNome")).andReturn("Marco");

        expect(requestMock.getParameter("vNota1")).andReturn("70");

        expect(requestMock.getParameter("vNota2")).andReturn("70");

        expect(requestMock.getParameter("vNotaFinal")).andReturn("0");

        expect(requestMock.getParameter("vFrequencia")).andReturn("75");

        replay(requestMock);

        ServletControllerWeb servletControllerWeb = new ServletControllerWeb();

        assertTrue(servletControllerWeb.verificarAprovacao(requestMock));

        }
        

Reprovação por Nota Final

Para ser reprovado por nota final, a média final ((média anterior + nota final) / 2) deve ser inferior a “50”. Para criar o caso de teste que represente esta situação, vamos seguir o modelo na Listagem 8. O método se chamará testAlunoReprovadoFinal().

Listagem 8. Implementação do Caso de Teste testAlunoReprovadoFinal()

        public void testAlunoReprovadoFinal() {

        HttpServletRequest requestMock = createMock(HttpServletRequest.class);

        expect(requestMock.getParameter("vNome")).andReturn("Marco");

        expect(requestMock.getParameter("vNota1")).andReturn("30");

        expect(requestMock.getParameter("vNota2")).andReturn("30");

        expect(requestMock.getParameter("vNotaFinal")).andReturn("69");

        expect(requestMock.getParameter("vFrequencia")).andReturn("75");

        replay(requestMock);

        ServletControllerWeb servletControllerWeb = new ServletControllerWeb();

        assertFalse(servletControllerWeb.verificarAprovacao(requestMock));

        }
        

Aprovação por Nota Final

Finalmente, para a implementação do último caso de testes do estudo de caso, seguiremos a implementação descrita na Listagem 9.

Listagem 9. Implementação do Caso de Teste testAlunoAprovadoFinal ()

        public void testAlunoAprovadoFinal() {

        HttpServletRequest requestMock = createMock(HttpServletRequest.class);

        expect(requestMock.getParameter("vNome")).andReturn("Marco");

        expect(requestMock.getParameter("vNota1")).andReturn("30");

        expect(requestMock.getParameter("vNota2")).andReturn("30");

        expect(requestMock.getParameter("vNotaFinal")).andReturn("70");

        expect(requestMock.getParameter("vFrequencia")).andReturn("75");

        replay(requestMock);

        ServletControllerWeb servletControllerWeb = new ServletControllerWeb();

        assertTrue(servletControllerWeb.verificarAprovacao(requestMock));

        }
        

Para nos certificarmos que será executado o trecho do método verificarAprovacao() que verifica se o aluno passou com a nota da final (e não somente com as duas primeiras notas), foram utilizados valores para vNota1 e vNota2 que retornariam que o aluno foi reprovado por nota. Ao informar o valor “70” no parâmetro vNotaFinal, verifica-se que o aluno foi aprovado.

Com isso, a classe que testa o método verificarAprovacao() do servlet está concluída, e com testes unitários automatizados, sem a necessidade de execução da aplicação via browser. Perceba que o intuito desde o início era testar o servlet, não a classe de domínio. Apenas para testar a classe de domínio, o framework JUnit seria suficiente. Para finalizar, a Figura 5 mostra a execução de todos os casos de teste implementados.

Resultado Final da Execução do Arquivo TestesAprovacao
Figura 5. Resultado Final da Execução do Arquivo TestesAprovacao

Verificando se todos os métodos treinados foram executados

Uma situação que ainda vale ser explorada é se todos os métodos treinados foram realmente executados em um teste. A Listagem 10 apresenta uma modificação feita no método testAlunoReprovadoFinal. Perceba que foi inserida uma linha antes do comando replay, treinando o mock para retornar a matrícula de um aluno.

Listagem 10. Caso de Teste testAlunoReprovadoFinal() modificado

        public void testAlunoReprovadoFinal() {

        HttpServletRequest requestMock = createMock(HttpServletRequest.class);

        expect(requestMock.getParameter("vNome")).andReturn("Marco");

        expect(requestMock.getParameter("vNota1")).andReturn("30");

        expect(requestMock.getParameter("vNota2")).andReturn("30");

        expect(requestMock.getParameter("vNotaFinal")).andReturn("69");

        expect(requestMock.getParameter("vFrequencia")).andReturn("75");

        expect(requestMock.getParameter("vMatricula")).andReturn("200821010");

        replay(requestMock);

        ServletControllerWeb servletControllerWeb = new ServletControllerWeb();

        assertFalse(servletControllerWeb.verificarAprovacao(requestMock));

        }
        

Neste caso, o teste continuará executando normalmente, pois a nova linha inserida não será chamada em nenhum momento pelo servlet, quando o teste for executado. Entretanto, poderíamos querer saber se todos os métodos treinados foram executados ao menos uma vez. Para isso, pode-se utilizar o comando verify(), conforme a última linha da Listagem 11.

Listagem 11. Caso de Teste testAlunoReprovadoFinal () com o comando verify()

        public void testAlunoReprovadoFinal() {

        HttpServletRequest requestMock = createMock(HttpServletRequest.class);

        expect(requestMock.getParameter("vNome")).andReturn("Marco");

        expect(requestMock.getParameter("vNota1")).andReturn("30");

        expect(requestMock.getParameter("vNota2")).andReturn("30");

        expect(requestMock.getParameter("vNotaFinal")).andReturn("69");

        expect(requestMock.getParameter("vFrequencia")).andReturn("75");

        expect(requestMock.getParameter("vMatricula")).andReturn("200821010");

        replay(requestMock);

        ServletControllerWeb servletControllerWeb = new ServletControllerWeb();

        assertFalse(servletControllerWeb.verificarAprovacao(requestMock));

        verify(requestMock);

        }
        

Executando os testes desta forma, pode-se verificar que o EasyMock irá apresentar uma falha, uma vez que um determinado método que foi treinado não foi executado.

Conclusão

O objetivo deste artigo foi demonstrar a importância dos testes unitários automatizados no processo de desenvolvimento de software e como sua utilização pode ser implementada. Foi mostrada uma solução eficaz para situações onde é necessário testar objetos que necessitam de outros que ainda não foram implementados, ou simplesmente quando se deseja isolar determinados objetos a serem testados, eliminando a dependência deles com outros existentes. Esta solução baseia-se na utilização de objetos mock, ou objetos “falsos”, através de frameworks específicos para este fim, como o EasyMock, apresentado neste artigo.

Revista Engenharia de Software 6
Esse artigo faz parte da revista Engenharia de Software 6 edição especial. Clique aqui para ler todos os artigos desta edição

Confira também