Cobertura de testes com Emma

Quando desenvolvemos testes automatizados para uma aplicação é importante avaliar se eles estão alcançando todas as partes do sistema. O Emma é uma ferramenta open source que ajuda nesta tarefa, fazendo a análise da cobertura de testes de um projeto Java e gerando um relatório em formato texto ou HTML. Esse relatório representa um feedback importante para os desenvolvedores, pois indica quais áreas do projeto ainda não estão sendo cobertas por testes automatizados e portanto devem ser tratadas prioritariamente.

Neste artigo veremos os conceitos fundamentais do Emma e como funciona a ferramenta. Também mostraremos como instalar e usar o Emma e como analisar a cobertura de testes em suas aplicações.

Exemplo de uso

Antes de descrever em detalhes o funcionamento do Emma, vamos ver através de um exemplo o tipo de resultado que ele pode nos prover. Para isso, iremos implementar uma pequena classe, contendo um único método. Esse método recebe um inteiro positivo e retorna o número correspondente na seqüência de Fibonacci. O exemplo pode ser acompanhado mesmo sem saber detalhes de como calcular os números de Fibonacci. Em todo o caso, a regra de geração desses números é simples:

fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(n) = fibonacci(n-2) + fibonacci(n-1)

Se o método recebe um número maior que 1, o retorno é a soma de cada um dos números de fibonacci de seus dois antecessores. Por exemplo, fibonacci(10) = fibonacci(8) + fibonacci(9).

O código da classe que contém o método fibonacci() encontra-se na Listagem 1 e a classe de teste correspondente está na Listagem 2. Mais adiante veremos como gerar o relatório do Emma indicando a cobertura de teste da classe, mas por enquanto você já pode ver o resultado da análise do Emma na Figura 1.

  
    package jm;

    public class Matematica {
        public int fibonacci(int numero) {
            if (numero >= 0) {
                if (numero == 0) return 0;
                if (numero == 1) return 1;
                return fibonacci(numero-1) + fibonacci(numero-2);
            } 
            throw new IllegalArgumentException();
        }
    }

Listagem 1. Classe que contém o método fibonacci().

  
    package jm;

    import junit.framework.TestCase;

    public class MatematicaTeste extends TestCase {
        Matematica matematica = new Matematica();

        public void testFibonacciZero() {
            verificaFibonacci(0, 0);
        }
        
        public void testFibonacciUm() {
            verificaFibonacci(1, 1);
        }
        
        public void testFibonacciDois() {
            verificaFibonacci(1, 2);
        }
        
        public void testFibonacciTres() {
            verificaFibonacci(2, 3);
        }
        
        public void testFibonacciQuatro() {
            verificaFibonacci(3, 4);
        }
        
        public void testFibonacciCinco() {
            verificaFibonacci(5, 5);
        }
        
        public void testFibonacciSeis() {
            verificaFibonacci(8, 6);
        }
        
        public void testFibonacciParaNumeroInvalido() {
            try {
                matematica.fibonacci(-1);
                fail("Deveria ter lançado IllegalArgumentException");
            } catch (IllegalArgumentException e) {
                assertTrue(true);
            }
        }
        
        private void verificaFibonacci(int resultadoEsperado, int valor) {
            assertEquals(resultadoEsperado, matematica.fibonacci(valor));
        }
    }

Listagem 2. Classe de teste usada para verificar o funcionamento do método fibonacci().

Figura 1. Resumo da cobertura de testes do projeto inteiro.
Figura 1. Resumo da cobertura de testes do projeto inteiro.

No topo desse relatório, o Emma indica o percentual de cobertura das classes, métodos e blocos (explicados mais adiante). Por exemplo, a análise da cobertura de linhas indica 100% (6/6), significando que todas as linhas executáveis da classe Matemática foram exercitadas pelos testes.

Abaixo dessa informação, o Emma apresenta um resumo da quantidade de pacotes no projeto, bem como arquivos, classes, métodos e linhas executáveis. Por último, o Emma mostra a análise dos pacotes do sistema, informando o percentual de cobertura das classes de cada um, bem como de seus respectivos métodos, blocos e linhas.

No relatório do Emma, é possível obter mais detalhes sobre a cobertura de cada pacote, clicando em seu nome. Nesse exemplo, ao clicar no pacote jm o Emma nos apresenta mais informações sobre ele, como ilustrado na Figura 2. No topo aparece um resumo do percentual de cobertura das classes, métodos, blocos e linhas do pacote. Mais abaixo, esses mesmos percentuais são apresentados novamente, agrupados por classe. Nesse caso em particular, só há uma classe, para a qual o Emma indica 100% de cobertura.

Figura 2. Resumo da cobertura de testes do pacote jm.
Figura 2. Resumo da cobertura de testes do pacote jm.

O Emma permite que você se aprofunde ainda mais na análise de cada classe da aplicação. Por exemplo, no relatório de um pacote, você pode clicar sobre o nome de cada classe para ver seu código, e assim visualizar que linhas estão sendo cobertas. No exemplo, se clicarmos em Matematica.java, o Emma nos apresentará a tela ilustrada na Figura 3.

Figura 3. Análise de cobertura da classe Matematica.
Figura 3. Análise de cobertura da classe Matematica.

No topo, vemos um resumo da cobertura do arquivo Matematica.java como um todo. Logo abaixo, o Emma apresenta a cobertura por classes contidas no arquivo. Para cada classe são apresentados os percentuais de cobertura agrupados por métodos. Por fim, o Emma mostra o código fonte do arquivo e colore com verde todas as linhas que são plenamente exercitadas quando os testes são executados. Quando uma linha não é executada, o Emma a realça em vermelho; já quando a linha foi parcialmente executada a cor amarela é usada. A idéia de uma linha parcialmente executada é explorada com mais detalhes adiante.

Exemplo de projeto com deficiências de cobertura

Para compreender o que aconteceria se o projeto não estivesse 100% coberto pelos testes, iremos apagar o método de teste testeFibonacciParaNumeroInvalido() apresentado na Listagem 2. Fazendo isso, o Emma nos mostraria o relatório ilustrado na Figura 4.

Figura 4. Resumo da cobertura de testes inferior a 100%.
Figura 4. Resumo da cobertura de testes inferior a 100%.

Nessa nova análise, o Emma indica que todas as classes e métodos continuam sendo executados pelos testes, entretanto verifica-se que o percentual de cobertura de blocos e linhas foi reduzido para 87% e 83% respectivamente. Clicando-se sobre o pacote jm, e depois sobre o nome da classe, somos levados mais uma vez ao código fonte, como ilustrado na Figura 5.

Figura 5. Cobertura parcial da classe Matematica.
Figura 5. Cobertura parcial da classe Matematica.

O Emma marca em vermelho a linha com código que não foi executado em nenhum momento pelos testes. Essa é uma das informações mais úteis que os desenvolvedores podem obter dessa ferramenta, já que representa um feedback concreto sobre as partes da aplicação que não estão sendo testadas e, portanto, mais têm potencial de produzir erros quando o sistema estiver em produção.

Preparando o ambiente

Agora que já vimos o que o Emma pode fazer por nossos projetos, vamos mostrar como colocá-lo em funcionamento. O primeiro passo é fazer o download do Emma aqui. O segundo é configurar o Eclipse, que escolhemos como IDE para este artigo.

Criando o projeto e os diretórios

No Eclipse, crie um novo projeto Java chamado Fibonacci e configure os diretórios onde serão armazenados os códigos fontes. Haverá dois diretórios, um para o código da aplicação (src) e outro para os testes (srcTeste). Para efetuar essa configuração, escolha Project|Properties, clique na opção Java Build Path no painel à esquerda e depois na aba Source à direita. Depois clique no botão Add Folder, digite src e clique em Ok.

Nesse momento o Eclipse irá perguntar se você deseja direcionar os arquivos compilados para o diretório bin. Aceite essa sugestão. Agora só falta adicionar o diretório srcTeste. Clique novamente no botão Add Folder, em seguida no botão Create New Folder, e digite srcTeste. Com isso, os diretórios onde serão armazenados os fontes estarão configurados.

Configurando o JUnit e o Emma

Para executar testes automatizados, usaremos o JUnit. Para isso, é necessário adicionar a biblioteca junit.jar ao classpath do projeto. Escolha Project|Properties, clique na opção Java Build Path no painel à esquerda e depois na aba Libraries à direita. Clique no botão Add External JARs e selecione o arquivo junit.jar, que já vem com o Eclipse no subdiretório plugins/org.junit_3.8.1 (a versão pode variar).

Finalmente, descompacte o arquivo do Emma que você obteve, crie um diretório lib no Eclipse e coloque nele os seguintes arquivos do Emma: emma.jar e emma_ant.jar. Para que o Eclipse reconheça esses arquivos, escolha a opção de menu File|Refresh.

Adicionando os arquivos do projeto

Crie a classe Matematica, clicando com o botão direito no diretório src e escolhendo a opção New>Class. Forneça o nome e defina o pacote como jm. Copie para a classe o código apresentado na Listagem 1.

Agora adicione a classe de teste. Clique com o botão direito no diretório srcTeste, escolha a opção New>Class e dê à classe o nome MatematicaTeste. Ela também deverá ser colocada no pacote jm. Copie para a classe o código da Listagem 2. Após a execução desses passos, seu projeto deverá estar organizado como ilustrado na Figura 6. Há um arquivo adicional na figura, chamado build.xml, que você adicionará ao projeto mais adiante.

Figura 6. Estrutura do projeto.
Figura 6. Estrutura do projeto.

Agora você pode executar o JUnit para verificar se tudo está funcionando corretamente. Clique com o botão direito sobre a classe MatematicaTeste e escolha Run As>JUnit Test. O JUnit deverá apresentar um resultado semelhante ao que aparece na Figura 7.

Figura 7. Execução dos testes no JUnit.
Figura 7. Execução dos testes no JUnit.

Executando o Emma

É possível executar o Emma através da linha de comando ou através do Ant. O uso do Ant é o mais interessante, pois permite automatizar a análise de cobertura, e portanto executá-la facilmente diversas vezes ao longo do projeto.

No Eclipse, clique com o botão direito sobre o nome do projeto, selecione New>File e digite build.xml. Em seguida, coloque nesse arquivo o conteúdo da Listagem 3. Para executar esse buildfile, clique com o botão direito sobre o arquivo e escolha a opção Run As>Ant Build...

Na tela apresentada, selecione a aba Classpath, clique em User Entries e em seguida no botão Add JARs. Selecione todos os jars do diretório lib (emma.jar e emma_ant.jar). Agora clique no botão Add External JARs e selecione o arquivo junit.jar no subdiretório plugins/org.junit_3.8.1. Clique em Ok e posteriormente em Run.


    
        
        
        
        
        
        

        
        
        
        

          
        


        
        

        
        
            
            
                
            
        

        
        
            
        

        
        
            
        

        
        
            
            
        

        
        
            
                
            
        

        
        
            
                
                
            
        

        
        
            
                
            
        

        
        
            
                
                    
                    
                    
                
                
                

                
                
            
        

        
        
            
            
                
                    
                        
                    
                    
                
            
            
        
        
        
            
            
        
        
    

Listagem 3. Script do Ant para executar o Emma.

Após a execução do Ant, você encontrará um novo diretório no seu projeto chamado emma/relatorio. Nele haverá um arquivo index.html que você poderá abrir em seu navegador web para ver o resultado da análise.

Compreendendo o funcionamento do Emma

O Emma é uma ferramenta de cobertura puramente baseada em instrumentação de bytecode. Isso significa que ele lê os bytecodes de sua aplicação (nos arquivos .class) e faz alterações nos mesmos, para que, além de executarem as instruções do aplicativo, também capturem informações para serem usadas na geração do relatório de cobertura. Para que você compreenda o funcionamento do Emma e o que significa instrumentação na prática, iremos explicar cada passo executado pelo script build.xml, apresentado na Listagem 3.

A execução do build.xml envolve os seguintes alvos (targets) do Ant, que são executados na seqüência indicada abaixo:

  1. init – Apaga o diretório bin e o recria novamente, com o objetivo de eliminar qualquer código previamente compilado.
  2. compilaSrc – Compila apenas as classes da aplicação e coloca os arquivos .class no diretório bin criado no alvo anterior.
  3. instrumenta – Nesse alvo o Emma é acionado pela primeira vez. Ele lê os arquivos .class armazenados no diretório bin, e os instrumenta, criando uma cópia deles em outro diretório. Nessa cópia, além dos bytecodes originais, outros são adicionados com o objetivo de apoiar o funcionamento do Emma. Na Figura 8, é possível acompanhar visualmente o que acontece. Note que após a instrumentação, o arquivo .class instrumentado recebeu novas instruções (em vermelho), que serão importantes em outra etapa. Ainda neste alvo, o Emma gera um arquivo chamado metadado.emma (os nomes dos arquivos gerados são definidos no buildfile). Esse arquivo contém informações importantes sobre a estrutura das classes instrumentadas, que serão usadas depois durante a criação do relatório em HTML.
  4. compilaTeste – Agora que os bytecodes da aplicação já foram instrumentados, podemos compilar os testes. Preferimos esperar para compilá-los nesse momento para evitar que as próprias classes de testes sejam instrumentadas e acabem sendo mostradas no relatório do Emma. O relatório final do Emma deve, é claro, conter apenas informações sobre a cobertura das classes da aplicação.
  5. testa – Esse é o momento no qual o JUnit é executado. Ele é direcionado para testar a aplicação usando as classes instrumentadas pelo Emma. Isso fará com que os códigos inseridos pelo Emma (em vermelho na Figura 8) também sejam executados. Estes códigos especiais gravam informações no arquivo cobertura.emma, as quais indicam precisamente que instruções do arquivo .class foram exercitadas pelos testes. Ao final da execução do JUnit, o arquivo cobertura.emma passa a conter informações sobre todas as instruções da aplicação executadas pelo JUnit.
  6. emma – Aqui ocorre a finalização do trabalho do Emma. A ferramenta gera o relatório em HTML, a partir das informações armazenadas anteriormente, nos arquivos metadado.emma e cobertura.emma.

Figura 8. Funcionamento básico do Emma.
Figura 8. Funcionamento básico do Emma.

Esse modelo de funcionamento permite coletar informações sobre a cobertura de testes de forma simples, independentemente da infra-estrutura sendo utilizada. O Emma apenas demanda o uso de uma JVM e obtém estatísticas de cobertura de forma não-intrusiva, com apenas um pequeno overhead de performance, e agindo silenciosamente durante a execução dos testes.

Mais sobre o resultado da análise

A unidade fundamental de cobertura usada no Emma é o bloco básico ou simplesmente bloco. Todas as demais unidades são derivadas desta. Um bloco é uma seqüência de instruções em bytecode sem jumps. Em outras palavras, ele sempre executa como uma unidade atômica (na ausência de exceções). Um bloco é considerado coberto quando o controle alcança sua última instrução. Um bloco coberto é, portanto, garantido de ter executado sem falhas ao menos uma vez.

À medida que se cria lógicas condicionais no código Java (como em loops e blocos case ou if), são criados novos blocos. Assim, 100% de cobertura de blocos significa 100% de linhas executáveis cobertas.

Com relação à cobertura de linhas, o que o Emma faz é descobrir como os blocos mapeiam para as linhas de código Java. Então, para cada linha, o Emma determina se todos os blocos básicos pelos quais a linha é responsável foram cobertos (lembrando que os blocos básicos são seqüências de instruções em bytecode, portanto não há um mapeamento um-para-um entre essas instruções e linhas Java). Se todos esses blocos tiverem sido cobertos, a linha é considerada 100% coberta. Senão a cobertura será parcial, e o Emma colore a linha com amarelo.

Veja exemplos de cobertura parcial no código ilustrado na Figura 9 (extraída da documentação do Emma). Na linha 6, apenas um ramo da condicional é executado. Já na linha 8, a variável vk nunca é incrementada, portanto a linha não tem cobertura de 100%. Note ainda que, como o corpo do loop nem o construtor default são executados, as linhas 10 e 14 são marcadas em vermelho, indicando ausência de cobertura.

Figura 9. Exemplo de cobertura parcial.
Figura 9. Exemplo de cobertura parcial.

No Emma, para que uma classe seja considerada coberta, é preciso antes que seja considerada executável. Uma classe executável é indicada como tendo sido coberta se tiver sido carregada e inicializada pela JVM. O Emma relata a cobertura de classes de modo que você possa descobrir as que parecem não ter sido "tocadas" pelos seus testes: elas podem ser código “morto” ou com necessidade de atenção por testes.

Finalmente, o Emma considera um método coberto quando o processamento tiver entrado nele (ou seja, se seu primeiro bloco tiver sido coberto). Verificar se o método completou a execução seria problemático: um dado método pode ter vários pontos de saída, e não fica claro qual desses caminhos devem ser considerados o "oficial".

Procurar por métodos que não tenham sido cobertos é uma boa técnica para detectar código “morto”, ou que necessite de mais atenção por parte dos testes.

Analisando a cobertura de suas aplicações

Talvez você esteja se perguntando como incorporar o uso do Emma aos seus projetos atuais. O procedimento é simples, e para exemplificá-lo mostraremos como utilizá-lo em conjunto com um projeto open source, que pode ser obtido facilmente na internet. Escolhemos o EasyMock, uma importante ferramenta para auxiliar na criação de mock objects (usados para apoiar a realização de testes unitários), mas qualquer outro projeto contendo testes unitários seria adequado.

Baixe o EasyMock aqui. Ao descompactar o download, temos acesso aos arquivos src.zip e test.zip, entre outros. Para usar o Emma, basta executar os seguintes passos:

  1. Crie uma cópia do projeto Fibonacci (para simplificar, vamos re-aproveitar parte da estrutura desse projeto).
  2. Nessa cópia, apague todos os arquivos do diretório src. No lugar deles, coloque os códigos fontes do EasyMock, descompactando o arquivo src.zip.
  3. Apague todos os arquivos do diretório srcTeste e no lugar deles coloque o conteúdo do arquivo test.zip.
  4. Finalmente, edite o arquivo build.xml, e substitua a linha <property name="suite.testes" value="jm.MatematicaTeste"/> por <property name="suite.testes" value="org.easymock.tests.AllTests"/>.
  5. Execute o build.xml. O relatório gerado é ilustrado na Figura 10.

Figura 10. Análise de cobertura de uma ferramenta open source.
Figura 10. Análise de cobertura de uma ferramenta open source.

Como se vê, usando o script do Ant apresentado nesse artigo, é fácil utilizar o Emma em projetos previamente existentes. O que fizemos no caso do EasyMock foi basicamente configurar os diretórios com os códigos fontes apropriados e, no arquivo build.xml, definir na propriedade suite.testes o nome da suite que contém todos os testes da aplicação (org.easymock.tests.AllTests).

Para usar o Emma em seu projeto, você pode seguir um processo similar. Configure seus arquivos de código fonte e de código de testes nos diretórios sugeridos, e faça o script do Ant apontar para a sua suite de testes.

Conclusões

Emma é uma ferramenta que indica as partes do código fonte de uma aplicação que estão sendo exercitadas por testes automatizados. É útil, entre outras coisas, para detectar partes da aplicação que não estejam sendo testadas adequadamente ou simplesmente não estão sendo usadas. Esse tipo de feedback pode ser usado para ajudar o desenvolvedor a priorizar novos testes. O Emma usa instrumentação de bytecode para coletar informações sobre cobertura de forma silenciosa, simples e com baixo overhead de desempenho.

Aproveite e veja como facilitar a automação de testes de unidade usando mock objects.

Download do Exemplo

Baixe os fontes do exemplo de Fibonacci aqui.

Autoria

Texto de Vinícius Manhães Teles.
Ilustrações de Leandro Mello.

Publicado em 15/10/2006.

Licenciado como Creative Commons Atribuição.