17/09/13 Tecnologias DevMedia - Leitor Digital Revistas Cursos Pocket videos DevWare Fórum Serviços Publicar Seja bem vindo, Lineu Mialaret! Comprar Fale conosco Meus Serviços [easy Java Magazine 16 - Índice] Go s t ei (0) Testes Unitários com JUnit Verificando os métodos de sua classe De que se trata o artigo Neste artigo aprenderemos um pouco sobre Testes de Software, sobre o que é um Teste Unitário, veremos como utilizar o JUnit para criá-los e como implementá-los seguindo as recomendações da Engenharia de Software. Também entenderemos um pouco sobre Cobertura de Código para avaliarmos quão efetivos são os testes unitários criados. Em que situação o tema é útil O tema é útil para os desenvolvedores e times de desenvolvimento que procuram entregar softwares de qualidade. Com os testes de software é possível avaliar se o produto de software que se está entregando ao cliente está bem construído, se está confiável e é possível eliminar erros do mesmo. Resumo Devman Neste artigo fazemos uma breve introdução ao universo dos Testes de Software, mostrando alguns conceitos que devem acompanhar todos os envolvidos no desenvolvimento de um produto de software. Introduzimos quais são os níveis de teste e focamos nos Testes Unitários. Falamos um pouco sobre Cobertura de Código, métrica que nos ajuda a avaliar os testes unitários criados, e criamos uma aplicação de exemplo a partir da qual iniciamos criando os testes unitários utilizando o framework JUnit. Além disso, mostramos passo a passo como criar e aperfeiçoar os testes visando uma maior efetividade dos mesmos, com o objetivo final de entregar uma aplicação um pouco mais robusta, ou seja, que não quebra facilmente devido a erros de programação. O ser humano é suscetível a erros. Quando programamos, procuramos sempre desenvolver uma lógica que implemente o que é requisitado por alguém ou que está especificado em algum documento. Muitas vezes, sem perceber, cometemos pequenos deslizes ao programar, mas continuamos acreditando que nosso código está coerente e consistente. Para verificar, rodamos a nossa aplicação e observamos seu comportamento e suas respostas aos nossos cliques e dados que digitamos. Não ocorreu nenhum erro? Então está perfeito. Será mesmo? E se testarmos com outros dados? Ao inserirmos dados inválidos propositalmente, qual seria o resultado? O que aconteceria se um laço while fosse executado mais vezes do que o esperado? No caso de nosso método possuir um if/else, nosso dado de teste fez o if ser executado? O else foi testado em algum momento? Olhamos de novo, mais 10 vezes, e não conseguimos ver erro nenhum. Estamos certos de www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 1/18 17/09/13 DevMedia - Leitor Digital que o código está correto. Pelo menos, pressupomos que esteja. Para ampliarmos nossa certeza, vamos conhecer um pouco sobre Testes de Software e aprender algumas técnicas que nos mostram que, apenas com concentração e boa intenção, não conseguimos enxergar os problemas que estão presentes. O que é Teste de Software? Durante o processo de desenvolvimento, há os momentos de verificar se o software que estamos construindo está correto e se o que estamos desenvolvendo é o que o cliente ou o usuário realmente quer. Estes momentos de verificação fazem parte de um processo chamado Verificação e Validação, também referenciado como V & V, e devem ocorrer em várias fases da construção do software. Validação é o processo que visa garantir que se está construindo o programa que o cliente de fato deseja utilizar, com as funcionalidades que ele espera que estejam disponíveis. Ou seja, conseguimos compreender o que o cliente quer e o entregamos conforme acertado. Verificação é o processo de garantir que o produto de software está sendo bem construído, executando corretamente, mostrando as mensagens corretas, sem ocorrer falhas durante sua operação. Assim esperamos. Teste de Software é parte integrante de V & V e corresponde ao processo que consiste de todas as atividades do ciclo de vida referentes à avaliação do software, com o objetivo de se determinar três coisas: (1) que o mesmo satisfaz aos requisitos especificados; (2) que o mesmo está apto para ser liberado para o cliente; (3) e para encontrar defeitos, motivo que todos pensam ser o único. Portanto, testar um programa não é apenas procurar erros, é um processo bem mais amplo, com seus papéis, atividades e artefatos resultantes. Um Caso de Teste, que nós chamamos apenas de Teste, consiste de: • Um conjunto de valores de entrada para o sistema; • As ações a serem executadas pelo testador ou outro programa que interaja com o software que está sendo testado; • Os resultados esperados como consequências das ações executadas; e • As pós-condições, que são o resultado final gerado como consequência do que foi executado ao longo do teste. Para conhecer os vários tipos de teste e entender em que momento eles se aplicam, veja a Figura 1, conhecida como Modelo V, que mostra como as atividades de teste podem ser integradas em cada fase do ciclo de vida de desenvolvimento. Quatro tipos diferentes são mostrados, onde cada um deles é aplicado em um nível diferente dos artefatos da aplicação, desde um simples método até o sistema inteiro. Por isso eles definem os Níveis de Teste. Níveis de Teste - entendendo melhor Vamos entender estes “níveis” melhor. Após a codificação do sistema, criação das classes, definição de seus atributos e implementação de seus métodos, iniciamos o desenvolvimento dos Testes Unitários, que consiste em checar se os métodos de uma classe funcionam corretamente (este é o foco deste artigo). Depois que todos os métodos que interessam foram testados separadamente, inicia-se os Testes de Integração, onde se procura verificar a comunicação entre as classes. Por exemplo, procura-se verificar os resultados dos métodos de uma determinada classe quando chamados por métodos de outras. Nesta fase, vai-se integrando do nível mais granulado, de classe em classe, indo até componentes inteiros interagindo com outros, mas sempre observando as estruturas internas que estão sendo testadas nos códigos, suas condições e seus laços. Estes testes, os unitários e os de integração, são também chamados de “Testes de Caixa Branca”, porque estamos olhando o que há dentro do código-fonte para poder testá-los. Após a integração, se junta todos os componentes e demais recursos que compõem o sistema e começam-se os Testes de Sistema. Neste nível não se checa mais o código, mas se a aplicação está fazendo corretamente o que está especificado nos requisitos, testando-a www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 2/18 17/09/13 DevMedia - Leitor Digital através das interfaces gráficas, checando relatórios e demais artefatos gerados, simulando mais ou menos o que um usuário faria ao usar o sistema. Por último, os Testes de Aceitação são realizados, geralmente, pelos próprios clientes ou usuários, onde se procura verificar se as necessidades deles foram adequadamente atendidas e se o sistema oferece o que eles esperam. Se estiver OK, o sistema é aceito e vai para produção. Tanto os testes de sistema quanto os de aceitação, diferentemente dos dois primeiros, são chamados de “Testes de Caixa Preta”, pois não se sabe como o código está estruturado, não se tem a visão de dentro da caixa. Testa-se sem se preocupar com isso, pois o que interessa agora é a funcionalidade de alto nível, na visão do usuário ou cliente. Nota Muitas vezes ouvimos falar em versão beta de algum aplicativo. Isto nada mais é do que uma versão disponibilizada para um Teste de Aceitação a ser realizado por uma grande quantidade de usuários finais em um ambiente de uso real. As empresas procedem assim porque, muitas vezes, é impossível ao time de desenvolvimento ou de testes prever todas as situações de uso de uma ferramenta na prática. [abrir im age m e m jane la] Figura 1. Modelo V. Aprofundando em Testes de Unidade ou Unitários Testes de Unidade são testes que verificam a menor unidade de projeto do software que, em um sistema orientado a objetos, é o método. Sua meta é encontrar defeitos. Falando de forma mais prática, testes unitários são nada mais do que outra classe Java que você cria para testar seu código. Esta classe deve possuir métodos nos quais você instancia a classe que deseja testar, chama seus métodos passando os parâmetros necessários, recupera seu resultado e o avalia comparando com o que você esperava que fosse retornado. Veremos mais a frente como implementá-los. É importante criar testes que “exercitem” todos os métodos de uma classe e cada estrutura que há dentro de cada um deles, ou seja, ifs, elses, for, forEach, entre outras. “Exercitar” é um termo que significa executar um trecho de código com um teste. Portanto, é importante que criemos testes que exercitem todas as partes do código. Quando criamos testes unitários que atinjam a condição de exercitar muitas partes do código, dizemos que atingimos uma boa “cobertura de código”. “Cobertura” é uma indicação de quais linhas, e quais caminhos diferentes dentro dos métodos, conseguimos verificar nos nossos testes. Veja o tópico “Cobertura de Código” para entender melhor como se consegue uma boa cobertura verificando linhas e caminhos. Apenas para ilustrar, observe o código da Listagem 1. Vemos que há um método calcular() que possui um while que, por sua vez, possui um if/else. É importante observar que, ao criarmos testes unitários para este exemplo, devemos exercitar o laço while, o if e o else. Quantos testes unitários seriam necessários para isso? www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 3/18 17/09/13 DevMedia - Leitor Digital Para exercitar o while, precisamos que qtd tenha um valor menor do que 50, caso contrário, não ocorreria nenhuma iteração. Se o valor informado em qtd for igual a 10, exercitaríamos o while e o if. Teríamos, assim, um teste passando 10 para qtd. Se criássemos outro teste onde o valor passado fosse menor que 50, não sendo 10, teríamos exercitado o while e o else. Concluindo, precisaríamos de dois testes unitários para atingir uma boa cobertura do código da Listagem 1. Usando TDD - Test Driven Developme Nota nt ou Desenvolvimento Guiado Por Testes - o desenvolvedor, antes mesmo de começar a programar, inicia com a criação de um teste unitário e, só depois, implementa o método. Afinal, o teste é que guia o desenvolvimento. Se você vai iniciar um projeto ou alguma nova funcionalidade, pesquise esta técnica. Listagem 1. Exemplo de método a ser exercitado. public void calcular (int qtd) { while (qtd < 50) { if (qtd == 10) { // realiza alguma operacao. } else { // senão, realiza outra operacao. } } } Cobertura de Código Cobertura de Código é um método de análise que permite verificar quão efetivo foram os testes unitários. Com este método é possível ter a noção de quais partes do software foram, ou não, executadas (ou cobertas) pela suite de testes. Lembrando que quando executadas, dizemos que tais partes foram “exercitadas”. Para avaliar a cobertura de código, criaram-se várias métricas que realizam diversas verificações no código testado. Dentre as mais utilizadas estão: cobertura de comandos (statement coverage) e a cobertura de desvios (branch coverage). Vamos entendê-las nos parágrafos seguintes. A Cobertura de Comandos (statement coverage), também conhecida como cobertura de linha, mede que linhas foram exercitadas pelos testes unitários. Por exemplo, no código a seguir, é possível notar que se criarmos um único teste unitário passando para o methodA() os valores 5 e 4, apenas a linha do if, que soma ‘a´ + ‘b´, e a linha que retorna ‘c´ seriam exercitadas. Neste caso, não conseguiríamos exercitar todos os comandos do nosso método. Portanto, a cobertura de linhas do método não seria de 100%. public int methodA(int a, int b) { if(a > b) { c = a + b; } else { c = a - b; } return c; www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 4/18 17/09/13 DevMedia - Leitor Digital } Neste caso, cobrimos apenas um desvio, o do if. Portanto, também não temos 100% de cobertura de desvios. Para conseguirmos cobrir 100% das linhas deste código e cobrir todos os desvios, será necessário criar mais um teste unitário passando qualquer valor onde ‘b´ é maior que ‘a´, podendo ser 5 e 4, por exemplo. Com isso, conseguiríamos exercitar o else. Olhando a cobertura dos dois testes separadamente, nenhum deles consegue alcançar os 100%. Mas uma suite de testes, que é um conjunto destes, com os dois juntos atingem os objetivos, tanto o da cobertura de comandos, ou linhas, quanto o da cobertura de desvios. Assim, é fácil identificar que precisaríamos de dois testes unitários, mas em uma situação mais complexa, no dia a dia, não seria tão evidente. Por este motivo o uso destas técnicas faz a diferença. Criando nossa aplicação de exemplo Para ilustrar o uso de testes unitários com o JUnit, vamos criar uma aplicação de exemplo bem simples, mas que contém os elementos que são importantes de destacar na hora de testar as unidades de um sistema. Para isso, utilizaremos a IDE Eclipse Indigo. A nossa aplicação é responsável por fazer um controle de empréstimo de livros. Para isso, precisamos de uma entidade Livro para poder iniciar os trabalhos. A Listagem 2 mostra como seria a nossa classe. A mesma tem os atributos autor, titulo, emprestado e reservado. Estes dois últimos guardam o status em relação a sua disponibilidade, está emprestado (true) ou não (false) e reservado (true) ou não (false). Também possui um atributo chamado historico que é do tipo lista de Emprestimos, cuja classe pode ser vista na Listagem 3. Cada empréstimo da lista contém informações como quem foi o Usuario (Listagem 4) que pegou o livro emprestado, qual foi a data do empréstimo (dataEmprestimo), e qual foi a data da devolução (dataDevolucao), ambos do tipo Date, e a lista de livros locados pelo usuário. Livro possui o método emprestar(), que simplesmente verifica se o atributo emprestado está como false, antes de emprestá-lo setando true. Ao fazê-lo, também imprime uma mensagem no console do Eclipse/NetBeans informando que o livro foi emprestado com sucesso. Esta classe também possui o método consultarEmprestimosPorUsuario(), para o qual é passada uma instância da classe Usuario, na Listagem 4. Esta nossa aplicação de brinquedo tem o objetivo apenas de criar elementos com as estruturas necessárias para exemplificar o uso de testes unitários com o JUnit. Listagem 2. Classe Livro da nossa aplicação fictícia www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 5/18 17/09/13 DevMedia - Leitor Digital de controle de empréstimos. public class Livro { private String autor; private String titulo; private boolean emprestado; private boolean reservado; private List<Emprestimo> historico; public boolean emprestar(){ if(!this.isEmprestado()){ this.setEmprestado(true); System.out.println("Livro emprestado com sucesso."); } return this.isEmprestado(); } public List<Emprestimo> consultarEmprestimosPorUsuario(Usuario usuario) { List<Emprestimo> emprestimosPorUsuario = new ArrayList<Emprestimo> (); for (Emprestimo emprestimoUsuario : emprestimosPorUsuario) { if(emprestimoUsuario.getUsuario().equals(usuario)) { emprestimosPorUsuario.add(emprestimoUsuario); } } return emprestimosPorUsuario; } } Listagem 3. Classe Emprestimo da nossa aplicação fictícia de controle de empréstimos. public class Emprestimo { private Usuario usuario; private Date dataEmprestimo; private Date dataDevolucao; private List<Livro> livros; } Listagem 4. Classe Usuario com método equals(). public class Usuario { private String nome; private String matricula; private boolean emDebito; public Usuario(String nome, boolean emDebito) { super(); this.nome = nome; this.emDebito = emDebito; } @Override public boolean equals(Object obj){ if(obj instanceof Usuario){ Usuario outro = (Usuario) obj; return outro.getNome().equals(getNome()); } else { return false; } } } No método consultarEmprestimosPorUsuario() é necessário fazer uma comparação para saber se o www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 6/18 17/09/13 DevMedia - Leitor Digital usuário passado como parâmetro tem o mesmo nome do usuário que fez o empréstimo, portanto a classe Usuario sobrescreve o método equals(). Para comparações utilizando este método, é necessário sobrescrevê-lo, o implementando da forma que for desejada; no caso, comparando pelo atributo nome. Se fizéssemos a mesma comparação sem sobrescrevê-lo, duas instâncias de Usuario só seriam iguais se referenciassem exatamente o mesmo objeto, pois Java compararia a identidade do objeto, que é diferente para cada instância presente na JVM. Exposta a estrutura da nossa aplicação, que contém alguns erros que passaram despercebidos, vamos instalar o JUnit para que possamos verificar nossos métodos. Instalando o JUnit O JUnit é um framework de testes unitários desenvolvido em Java por iniciativa de Kent Beck e Erich Gamma. Ele já vem integrado nas IDEs mais atuais. No momento de criar seu projeto, basta adicioná-lo ao Java Build Path clicando no seu projeto e selecionando o menu File | Properties > Java Build Path, selecionando a aba Libraries e pressionando o botão Add Library, como mostra a Figura 2. Na tela Add Library, selecione a opção JUnit e pressione o botão Next. Então, mude a opção JUnit library version para JUnit 4, como mostra a Figura 3, e clique em Finish. A versão que vem pré-instalada no Eclipse Indigo é a versão 4.8.2. Outra opção seria acessar o site do JUnit e baixar a última versão (no momento da escrita deste artigo é a 4.10). Baixe o arquivo junit-<versao>.jar, crie uma pasta lib na raiz do seu projeto, se já não houver. Adicione seu arquivo jar nesta pasta. Faça um refresh do seu projeto para visualizar a pasta e o arquivo. Clique com o botão direito do mouse sobre o arquivo e selecione a opção Build Path > Add to Build Path. Ao concluir estes passos, seu projeto já estará apto para utilizar os recursos disponibilizados pelo JUnit para criação de testes unitários. [abrir im age m e m jane la] www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 7/18 17/09/13 DevMedia - Leitor Digital Figura 2. Adicionando o JUnit ao build path. [abrir im age m e m jane la] Figura 3. Alterando a versão do JUnit. Criando os testes unitários de nossa aplicação O nosso objetivo no momento é conseguir aplicar a teoria que vimos até então no software de exemplo que criamos no tópico anterior. Ao utilizar as técnicas apresentadas, conseguiremos criar bons testes unitários e, por consequência, obteremos uma boa “cobertura” com os mesmos. Antes de qualquer coisa, é uma prática criar os testes unitários em um pacote à parte, geralmente chamada test, dentro da pasta src. Procure criar os pacotes de teste com caminhos similares aos das classes testadas. Por exemplo, para as classes do pacote biblioteca.entidade, crie classes de teste em um pacote biblioteca.entidade.test. No final das contas, a estrutura de pastas de nosso projeto ficaria como indicado na Figura 4. [abrir im age m e m jane la] www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 8/18 17/09/13 DevMedia - Leitor Digital Figura 4. Estrutura de pastas do projeto. Geralmente, cria-se uma classe de teste para cada classe a ser testada do projeto. Para criar testes unitários para a nossa aplicação com o JUnit, é necessário selecionar uma classe. Assim, vamos utilizar Livro. Pressione o botão direito do mouse em cima dela. Ao abrir o menu, selecione New > JUnit Test Case. Uma tela como a da Figura 5 é exibida. Informe o nome que desejar para a classe de teste. O default é: <o nome da classe a ser testada>+“Test”, no nosso caso, LivroTest. [abrir im age m e m jane la] Figura 5. Tela de caso de teste. Na Figura 5, onde vemos o campo Package, indique o pacote de testes. No campo Name, você pode indicar o nome que quiser, possuindo ou não o sufixo “Test”. O mais interessante de observar é o conjunto de caixas de checagem que tem mais abaixo na Figura 5. Dentre elas, setUp() já vem marcada. Deixe assim - veremos mais adiante do que se trata, e clique em Next >. A próxima tela pode ser observada na Figura 6. Nela é possível ver a classe selecionada e os seus métodos. Ao selecioná-los, uma classe de teste será criada com testes apenas para os métodos que foram selecionados. Não é necessário criar testes para os www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 9/18 17/09/13 DevMedia - Leitor Digital gets e sets, a não ser que eles contenham alguma regra de negócio. Deste modo, marque os métodos emprestar() e consultarEmprestimosPorUsuario() e clique em Finish. [abrir im age m e m jane la] Figura 6. Selecionando os métodos a se testar. O resultado é o mostrado na Listagem 5. Observe que há três métodos: setUp(), testEmprestar() e testConsultarEmprestimosPorUsuario(). O primeiro foi criado automaticamente porque a caixa de checagem de mesmo nome, exibida na Figura 5, estava marcada. Este método está anotado com @Before, o que determina que será executado antes de qualquer método de teste desta classe. Nele, é possível criar qualquer configuração inicial que será utilizada pelos demais testes da classe, como instanciar e atribuir elementos a uma coleção, instanciar demais objetos necessários, conectar com algum recurso, entre outras coisas. Os dois métodos seguintes são os da classe Livro que selecionamos para teste, como indica a Figura 6, acrescidos da palavra “test” como prefixo, que não é obrigatório. Eles são anotados com @Test, o que determina para o JUnit quem são os métodos de teste. Listagem 5. Código da classe LivroTest. public class LivroTest { @Before public void setUp() throws Exception {} @Test public final void testEmprestar() { fail("Not yet implemented"); // TODO } @Test public final void testConsultarEmprestimosPorUsuario() { fail("Not yet implemented"); // TODO } } www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 10/18 17/09/13 DevMedia - Leitor Digital Para executar esta classe de teste recém-criada, basta clicar com o botão direito em cima da mesma, no Project Explorer, e selecionar Run As > JUnit Test. O JUnit abrirá uma view como a mostrada na Figura 7, onde é possível ver o tempo gasto na execução dos testes, quantos foram rodados do total (Runs: 2/2), quantos erros ocorreram (Errors: 0) e quantas falhas (Failures: 2). Um erro indica que ocorreu algum problema e o teste não foi concluído. Uma falha indica que o teste foi concluído e o resultado esperado não foi o desejado, portanto, há algum defeito na classe sendo testada. Ao clicarmos no método testEmprestar() na view do JUnit, por exemplo, é possível ver qual a razão do mesmo ter falhado observando a Failure Trace. Neste ponto, criamos uma classe de testes unitários e os executamos. O que nos falta agora é colocar a lógica de teste nestes métodos, instanciando as classes de nossa aplicação dentro deles, chamando seus métodos e verificando os resultados. [abrir im age m e m jane la] Figura 7. Resultado da execução inicial da classe LivroTest. Implementando nosso teste Após termos aprendido como criar e executar um teste unitário, vamos implementar a nossa classe de teste, LivroTest. Para começar, crie um membro Livro nela. Vamos instanciá-lo no método setUp(). Configure seus atributos da seguinte forma: o autor será “Jorge Amado”, o titulo será “Capitães da Areia” e determinaremos que o atributo reservado, passado no construtor, será false. Além disso, crie três instâncias de Emprestimo, determine o Usuario de cada uma delas, como mostrado na Listagem 6, e os adicione ao atributo historico, do objeto livro. Observe que o usuário “José” se repete, pois seria seu segundo empréstimo deste livro. Desta forma temos um livro de Jorge Amado que foi locado três vezes, duas das quais por José. O segundo parâmetro do construtor de Usuario é o status que indica se o mesmo está em débito ou não. Na terceira locação, representado pelo www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 11/18 17/09/13 DevMedia - Leitor Digital objeto emprestimo3, José estava com débitos na biblioteca. Este é o contexto no qual estamos configurando nosso teste da classe Livro. Listagem 6. Método setup() da classe LivroTest. public class LivroTest { Livro livro; @Before public void setUp() { livro = new Livro("Jorge Amado", "Capitães da Areia", false); Emprestimo emprestimo1 = new Emprestimo(); emprestimo1.setUsuario(new Usuario("Jose", false)); Emprestimo emprestimo2 = new Emprestimo(); emprestimo2.setUsuario(new Usuario("João", false)); Emprestimo emprestimo3 = new Emprestimo(); emprestimo3.setUsuario(new Usuario("Jose", true)); livro.getHistorico().add(emprestimo1); livro.getHistorico().add(emprestimo2); livro.getHistorico().add(emprestimo3); } // demais métodos omitidos. } Para cada “objeto de teste”, no caso cada método da classe Livro, é interessante realizar um “teste positivo”, no qual testamos uma situação de sucesso, e um “teste negativo”, no qual verificamos se nossa aplicação trata corretamente uma situação incorreta. A partir de agora, incrementaremos um pouco mais o nosso teste. Observando a classe Livro, na Listagem 2, o método emprestar() apenas verifica se o livro não está emprestado para que ele possa atribuir true ao atributo emprestado, ou seja, emprestá-lo. Caso esteja emprestado, não faz nada e retorna a situação atual do livro. Portanto, precisamos fazer dois testes: um positivo, no qual o livro não está emprestado e chamamos o método emprestar(); e outro negativo, no qual o livro já está emprestado e chamamos o método emprestar() mesmo assim. Observando os objetivos de cobertura que vimos antes, neste método temos uma estrutura if, portanto é importante criar duas situações: uma onde a condição lógica seja verdadeira, para entrar no mesmo; e outra onde a condição seja falsa, para não entrar e não realizar qualquer operação, para assim podermos avaliar o comportamento completo do método. É importante verificar as duas situações de uma condição lógica para podermos avaliar o que acontece com os objetos e variáveis manipulados pelo método caso as operações que deveriam ser realizadas dentro do if não se realizem. Alguns erros podem estar contidos aí. Com este fim, renomeamos o método da classe de teste testEmprestar() para testEmprestarLivroNaoReservado() e criamos o testEmprestarLivroReservado() que corresponde ao “teste negativo” de empréstimo de um livro. Estes dois www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 12/18 17/09/13 DevMedia - Leitor Digital métodos ficariam como na Listagem 7. Com o JUnit, a forma de verificarmos se o resultado da classe a ser testada é o esperado ou não é utilizando um conjunto de assertivas: assertTrue(), assertFalse(), assertEquals(), assertNull(), assertNotNull(), só para citar algumas. Portanto, se esperamos um valor lógico true como resultado de um método, utilizamos o assertTrue() passando como parâmetro o retorno do método. Se desejamos verificar que dois objetos são iguais, um retornado por um método e outro que você criou no corpo do método de teste unitário, utilizamos um assertEquals() passando os dois objetos como primeiro e segundo parâmetros. Listagem 7. Métodos da classe LivroTest. public class LivroTest { Livro livro; @Before public void setUp() { livro = new Livro("Jorge Amado", "Capitães da Areia", false); Emprestimo emprestimo1 = new Emprestimo(); emprestimo1.setUsuario(new Usuario("Jose", false)); Emprestimo emprestimo2 = new Emprestimo(); emprestimo2.setUsuario(new Usuario("João", false)); Emprestimo emprestimo3 = new Emprestimo(); emprestimo3.setUsuario(new Usuario("Jose", true)); livro.getHistorico().add(emprestimo1); livro.getHistorico().add(emprestimo2); livro.getHistorico().add(emprestimo3); } @Test public void testEmprestarLivroNaoReservado(){ assertTrue(livro.emprestar()); } @Test public void testEmprestarLivroReservado(){ livro.setReservado(true); assertFalse(livro.emprestar()); } // demais métodos omitidos. } Com a classe de teste mais completa, podemos executá-la novamente. Ao executar, é possível verificar que o teste testEmprestarLivroReservado() falha. Por que será? Se você observar, o nosso teste está correto, pois não podemos emprestar um livro que está reservado. Esta falha nos mostra um erro de lógica na nossa aplicação. Se um estudante, ou usuário da biblioteca, pegar um exemplar na estante e levar até a bibliotecária para pegá-lo emprestado, ela não precisa checar se o livro está emprestado ou não, visto que o mesmo estava disponível no acervo. Para os operadores do nosso sistema, isto não faz sentido: ou o livro está disponível www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 13/18 17/09/13 DevMedia - Leitor Digital nas dependências da biblioteca, ou já está emprestado para outro usuário. A implementação mais adequada do método emprestar(), da nossa classe Livro, seria como o mostrado na Listagem 8. Quanto à reserva, devemos verificar se o livro está ou não reservado e, por algum motivo, não estava separado dos demais, no setor de reservas. Caso esteja reservado, não se pode emprestá-lo. Caso contrário, o usuário poderá ter uma boa leitura no conforto do seu lar. O nosso teste nos mostrou uma falha negocial ao refletirmos sobre a lógica do método a ser testado. Ao executarmos os testes novamente, podemos verificar que agora ele passou. O ícone ao lado do nome do teste, na view do JUnit, se torna uma checagem verde ( ). Listagem 8. Nova implementação do método emprestar() da classe Livro. public boolean emprestar(){ if(this.isReservado()){ this.setEmprestado(false); System.out.println("Livro está reservado."); } else { this.setEmprestado(true); System.out.println("Livro emprestado com sucesso."); } return this.isEmprestado(); } Vamos agora implementar os testes do método consultarEmprestimosPorUsuario(). Este método possui uma lista dos empréstimos do livro e itera sobre ela buscando se o usuário é o mesmo que o passado por parâmetro, como mostrado na Listagem 2. Quando há laços, uma recomendação mais rigorosa nos exige criar pelo menos três testes: um teste onde executamos o laço pelo menos uma vez; outro onde não executamos nenhuma; e outro quando executamos mais de duas vezes. Porque não executar nenhuma? Às vezes, nós contamos que sempre ocorrerá várias iterações de um laço e implementamos o método contando com isso, definindo um conjunto de operações dentro do mesmo. Quando o laço não é executado nenhuma vez, e não contávamos com isso, alguma operação óbvia deixou de ser executada, comprometendo assim toda a função do método e, consequentemente, prejudicando quem aguardava o retorno do mesmo. A nossa classe de teste ficaria como mostra a Listagem 9. Listagem 9. Código da classe LivroTest. public class LivroTest { Livro livro; @Before public void setUp() { livro = new Livro("Jorge Amado", "Capitães da Areia", false); Emprestimo emprestimo1 = new Emprestimo(); emprestimo1.setUsuario(new Usuario("Jose", false)); www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 14/18 17/09/13 DevMedia - Leitor Digital Emprestimo emprestimo2 = new Emprestimo(); emprestimo2.setUsuario(new Usuario("João", false)); Emprestimo emprestimo3 = new Emprestimo(); emprestimo3.setUsuario(new Usuario("Jose", true)); livro.getHistorico().add(emprestimo1); livro.getHistorico().add(emprestimo2); livro.getHistorico().add(emprestimo3); } @Test public void testEmprestarLivroNaoReservado(){ assertTrue(livro.emprestar()); } @Test public void testEmprestarLivroReservado(){ livro.setReservado(true); assertFalse(livro.emprestar()); } @Test public void testNenhumEmprestimoPorUsuario(){ Usuario novoUsuario = new Usuario("Judas", false); List<Emprestimo> historico = livro.consultarEmprestimosPorUsuario(novoUsuario); int resultadoReal = historico.size(); assertEquals(0, resultadoReal); } @Test public void testUmEmprestimoPorUsuario(){ Usuario novoUsuario = new Usuario("João", false); List<Emprestimo> historico = livro.consultarEmprestimosPorUsuario(novoUsuario); int resultadoReal = historico.size(); assertEquals(1, resultadoReal); } @Test public void testDoisEmprestimosPorUsuario(){ Usuario novoUsuario = new Usuario("Jose", false); List<Emprestimo> historico = livro.consultarEmprestimosPorUsuario(novoUsuario); int resultadoReal = historico.size(); assertEquals(2, resultadoReal); } } Ao executarmos nossos testes agora, vemos que quase todos passaram, com exceção de testUmEmprestimoPorUsuario() e testDoisEmprestimosPorUsuario(). Quando analisamos o método consultarEmprestimosPorUsuario() da classe Livro, verificamos que há um erro muito elementar: esquecemos que o método itera por uma coleção que foi criada dentro dele mesmo. Como poderia ter alguma coisa se acabamos de criá-la? A correção deste problema consiste apenas em alterar a instrução for para que itere na coleção historico, membro da classe Livro, e não na coleção emprestimosPorUsuario, que é interna ao método e é, na verdade, seu objeto de retorno. Após esta correção, todos os testes passam com sucesso, como mostra a Figura 8. [abrir im age m e m jane la] www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 15/18 17/09/13 DevMedia - Leitor Digital Figura 8. Resultado dos testes com sucesso. Parece óbvio, mas qual o benefício mesmo? Realmente parece óbvio, pois quando estamos criando testes unitários, estamos criando métodos para testar outros métodos. E ao fazer isto, também estamos corrigindo nossa aplicação. Ao criá-los, devemos fazê-lo buscando verificar cada estrutura de cada método, para a maior quantidade de métodos possível de nossa aplicação. Tendo esta coleção de testes em mãos, toda vez que for necessário fazer alguma alteração nas regras de negócio ou corrigir algum erro encontrado por um usuário ou até mesmo uma melhoria, altere tranquilamente. Após fazer o que é necessário, basta reexecutar o seu arsenal de testes já pronto para verificar se o que você alterou, corrigiu ou melhorou, não danificou alguma parte do sistema que já estava funcionando. A consequência negativa que pequenas alterações podem acarretar ao sistema também é conhecida por “efeito gelatina”; como se sua aplicação fosse uma gelatina num pires sendo arrastado sobre uma mesa que, ao mexer, tudo treme. Analogamente ao desenvolvimento de sistemas, se você fizer uma pequena alteração, esta poderia causar algum tremor no seu sistema, pois, sem querer, poderia ter consequências em outras partes que se relacionam com a parte alterada. Os testes unitários evitam este efeito porque antes de liberar um build do sistema, você reexecutaria os testes unitários. Desta forma você se sentirá bem mais seguro ao mexer em um código que já existe e/ou que foi criado por outro programador. Conclusões Neste artigo, aprendemos sobre o que é Teste de Software, qual o seu papel no processo de desenvolvimento de software e quais os Níveis de Teste que são definidos nesta área específica da Engenharia de Software. Aprofundamos no nível específico dos Testes de Unidade (ou unitários), sua definição e seu papel dentro dos testes em geral. www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 16/18 17/09/13 DevMedia - Leitor Digital Aprendemos também a criar testes unitários utilizando um framework bastante conhecido, o JUnit, que, enquanto este artigo estava sendo escrito, sua versão mais recente era a 4.10. Ao longo do artigo, criamos uma aplicação de brinquedo apenas para ilustrar situações corriqueiras e importantes nas quais os testes de unidade podem ajudar bastante no processo de codificação de um sistema. Falamos superficialmente sobre um método de análise que nos permite avaliar quão efetivo estão os nossos testes unitários, método este que nos auxilia na necessidade de identificar se precisamos criar mais testes ou não, que são as métricas de cobertura de código. Assim, esperamos ter demonstrado de forma clara e didática para quê servem os testes unitários, como criá-los da melhor forma e qual a sua grande utilidade ao longo do desenvolvimento e manutenção de um sistema. João dos Prazeres Farias Mestre em Ciência da Computação com ênfase em automação de testes pela UFPE, bacharel em Ciência da Computação pela UNICAP. Professor do Instituto Superior Fátima, em Brasília, ministrando as disciplinas de Lógica de Programação e Projeto I e II. É desenvolvedor desde 1999 e trabalha com Java desde 2006. eduardo v inicius ferreira de oliv eira 31/3/2012 14:20 Boa tarde, estou tentando fazer os testes com o JUnit seguindo os passos do artigo mas estou com o seguinte erro: Could not connect to: : 49382 java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.connect0(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:69) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:337) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:198) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:180) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:157) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:391) at java.net.Socket.connect(Socket.java:579) at java.net.Socket.connect(Socket.java:528) at java.net.Socket.(Socket.java:425) at java.net.Socket.(Socket.java:208) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.connect(RemoteTestRunner.java:570) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:381) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 17/18 17/09/13 DevMedia - Leitor Digital O que devo fazer ? desde ja agradeço Responder Olá Eduardo, solicitei um dos nossos consultores a te ajudar, peço que aguarde um pouco. WESLEY YA MA ZA CK 4/2/2012 10:14:47 PM Obrigado e um abraço Responder Chegou a carregar o Bacno de DAdos/Servidor de Aplicação ? Este erro quer dizer que o servidor/banco estão baixados. Responder DYEGO SOUZA DO CA RMO 4/3/2012 8:44:36 A M DevMedia Curtir 13.551 pessoas curtiram DevMedia. DevMedia | Anuncie | Fale conosco Hospedagem web por Porta 80 Web Hosting 2013 - Todos os Dire itos R e se rvados a we b-03 P lug-in social do F acebook www.devmedia.com.br/websys.5/webreader.asp?cat=61&artigo=4419&revista=easyjavamag_16#a-4419 18/18