Testes Automatizados - · PDF fileé, então, implantada em...

9

Click here to load reader

Transcript of Testes Automatizados - · PDF fileé, então, implantada em...

Page 1: Testes Automatizados - · PDF fileé, então, implantada em produção. O portal começa a fazer bas- ... como no exemplo seguinte (introdução simplificada da canção “Rompendo

27

Testes Automatizados

ocê recebeu a incumbência de implementar a funcio-nalidade ABC do novo portal Web da empresa. O que você faz? Pensa um pouco no design, começa a escrever

código e no final faz alguns testes manuais para confirmar se tudo está correto. A equipe de qualidade (se houver uma) encontra alguns poucos problemas, você corrige e a funcionalidade ABC é, então, implantada em produção. O portal começa a fazer bas-tante sucesso entre seus usuários e a empresa aproveita o bom momento para solicitar a inclusão da funcionalidade EFG. Você recebe a solicitação e parte para o desenvolvimento. Contudo,

para a inclusão desta funcionalidade, você se dá conta que terá que fazer uma refatoração importante no sistema, com impactos nas funcionalidades já desenvolvidas. Com um “frio da barriga”, você realiza as transformações necessárias no código, faz alguns testes básicos (manuais) e se dá por satisfeito para prosseguir com a implementação da nova funcionalidade. Após entregar o softwa-re para a equipe de qualidade testar, você começa a ver diversos bugs sendo cadastrados numa ferramenta de bug tracking; com o detalhe de que vários destes bugs dizem respeito à funcionalidade ABC, que já estava funcionando.

: : COLUNA – Cinto de Utilidades: :

Com a popularidade dos métodos ágeis nos últimos anos, boas práticas como o uso de testes automatizados e integração contínua já são de conhecimento de boa parte dos desenvolvedores profissionais. Detecção precoce de bugs, feedback rápido e melhoria contínua no design das aplicações são apenas alguns benefícios que podemos citar. Apesar das vantagens, ainda existem os que preferem utilizar apenas testes manuais ad-hoc ou, pior ainda, a filosofia do “se compilou, então está funcionando!”

E você? Desenvolve software com o uso de testes automatizados? Ainda não? Então, continue lendo, pois neste artigo vamos passar por algumas das principais ferramentas utilizadas para a plataforma Java dentro do contexto de um processo de desenvolvimento orientado a testes.

Alexandre Gazola

([email protected] / Twitter: @alexandregazola): é bacharel em Ciência da Computação pela Universidade Federal de Viçosa (UFV) e mestre em Informática pela PUC-Rio. Trabalha como Analista de Sistemas no BNDES, desenvolve em Java desde 2003 e possui as certificações SCJP e SCWCD. É articulista e editor-técnico da revista MundoJ, além de manter um blog em http://alexandregazola.wordpress.com. Também está aprendendo a tocar violão e é um entusiástico estudante de idiomas.

Page 2: Testes Automatizados - · PDF fileé, então, implantada em produção. O portal começa a fazer bas- ... como no exemplo seguinte (introdução simplificada da canção “Rompendo

28

: : www.mundoj.com.br : :

A história poderia ter sido diferente se a funcionalidade tivesse sido desenvolvida com testes automatizados, que constituem uma boa rede de proteção contra a regressão do sistema1. A existência de testes automatizados também é pré-requisito para a refatoração constante do sistema (de forma segura). Esta, por sua vez, também é uma prática importante para que o design da aplicação evolua e melhore continuamente de forma a melhor acomodar as novas funcionalidades. É praticamente impossível construir um sistema OO usando um modelo de domínio rico (que é constantemente aperfeiçoado) sem aplicar refatoração constante. Daí a importân-cia de se ter uma boa suíte de testes automatizados.

Outro benefício relacionado ao uso dos testes também pode ser obtido quando usamos o desenvolvimento orientado a testes (TDD – Test-driven Development). Neste estilo, começamos a de-senvolver uma funcionalidade primeiramente escrevendo um teste automatizado, para só então implementar o código que deverá sa-tisfazer ao teste. Uma vez que o teste esteja passando, procedemos ao trabalho de melhorar o código produzido (i.e. refatoração) visto que já dispomos de um mecanismo de proteção para nos fornecer a devida segurança. Nesta maneira de se desenvolver, em vez de aplicar o tradicional ciclo design-codificação-testes, ao receber uma nova funcionalidade, aplicamos testes-codificação-design.

Neste artigo, iremos fornecer uma noção geral sobre TDD, com alguns exemplos básicos de ferramentas importantes utilizadas no mundo Java. Falaremos sobre os ciclos de feedback interno e externo no desenvolvimento orientado a testes, contextualizando-os na prática por meio das ferramentas JUnit, Mockito, DbUnit (com HSQLDb), JBehave, Selenium e EclEmma. Escolhemos a plataforma Java como exemplo para as ferramentas por ser esta de conhecimento da maioria dos leitores. Não obstante, o processo geral apresentado e as ferramentas mencionadas podem ser facil-mente mapeados para outras plataformas.

Ressaltamos que o propósito aqui não é de nos aprofundarmos em nenhuma técnica ou ferramenta, mas apenas expor o leitor ao processo de desenvolvimento com testes e algumas das ferramen-tas de suporte. Após ler o artigo, encorajamos fortemente o leitor a estudar cada uma das ferramentas citadas. Ao longo do texto e ao final, referenciamos alguns dos diversos artigos já publicados sobre esse tema na revista MundoJ, além de relacionarmos alguns bons livros sobre o assunto na seção de referências.

Ciclos de feedback

Desenvolvimento com testes

1Escrevi “poderia ter sido diferente, pois a qualidade desta rede de proteção está

intimamente relacionada à qualidade dos testes automatizados escritos.

2A inspiração para a aplicação de exemplo foi obtida a partir do artigo “Behaviour

Driven Development: By Example”, de Ryan Greenhall (ver referências).

3De fato, em Programação Extrema (XP), histórias são como lembretes para con-

versas futuras com o cliente.

4Em nosso exemplo, desprezamos a informação sobre em qual oitava as notas

musicais se encaixam.

Uma característica que permeia as práticas de desenvolvimento de software ágil é o estabelecimento de ciclos de feedback rápidos e contínuos ao longo do projeto (pense, por exemplo, em programa-ção em par, stand-up meetings, quadros kanban, cliente sempre presente etc.). Isso também é algo bem evidente quando desen-volvemos software utilizando testes automatizados, especialmente quando aplicamos o TDD. Nesta técnica, primeiramente escre-vemos um teste automatizado para o pedaço de funcionalidade a ser desenvolvida, e só depois, então, é que implementamos a funcionalidade propriamente dita. Dessa forma, temos um crité-rio para sabermos quando realmente acabamos a implementação; conseguimos saber se o código que acabou de ser criado tem uma boa qualidade, ou seja, se ele atende nossas expectativas expressas por meio dos testes automatizados. Para cada nova funcionalidade

sendo criada, temos feedback instantâneo e localizado a respeito do seu grau de “corretude”. Além disso, o número de testes para o software cresce continuamente, provendo uma rede de seguran-ça que ajuda a proteger o sistema contra a introdução de novos bugs (os chamados testes de regressão). Vale frisar que o TDD é primariamente uma técnica de design (com o grande benefício colateral de nos prover uma boa suíte de testes de regressão), pois o exercício de escrever o teste antes nos força a modelar o sistema de tal forma que ele seja fácil de testar, o que por sua vez acaba desencadeando um design com baixo acoplamento.

Testes automatizados podem nos ajudar a garantir tanto qualidade externa quanto interna num sistema de software. Qualidade exter-na está relacionada às funcionalidades do software em si, percep-tíveis pelo usuário da aplicação (o sistema atende à necessidade de negócio de seus usuários de forma satisfatória?). Qualidade interna está relacionada ao código do sistema (o código é legível e de fácil manutenção?). Para endereçar a qualidade externa do sistema, podemos fazer uso de testes de aceitação (usando uma abordagem de Acceptance TDD ou de Behavior-driven Develop-ment) e para endereçar a qualidade interna do sistema podemos fazer uso de testes de unidade (usando uma abordagem de TDD). Essas duas abordagens, utilizadas em conjunto, podem nos prover valiosos ciclos de feedback ao longo do desenvolvimento de um sistema de software.

Ao longo do artigo, vamos mostrar algumas ferramentas Java importantes para desenvolvermos software com qualidade (tanto do ponto de vista interno quanto externo). Para isso, vamos uti-lizar a aplicação fictícia de exemplo do quadro 1 como ponto de partida2.

Considerando a aplicação descrita no quadro 1, vamos mostrar como podemos acrescentar novas funcionalidades a ela por meio do desenvolvimento orientado a testes. Neste processo, introduzi-remos algumas das ferramentas de suporte.

Vamos supor que nosso cliente priorizou a seguinte história para ser desenvolvida na próxima iteração:

Como músicoDesejo enviar uma nova tablaturaPara compartilhá-la com outras pessoas

Como pode ser notado, a história não diz muita coisa que possa nos ajudar em termos de implementação3. Precisamos conver-sar com o cliente sobre o que de fato o sistema deve fazer para satisfazer essa história, ou seja, quais seus critérios de aceitação ou cenários de uso. Numa conversa com o cliente, levantamos o seguinte cenário4:

Page 3: Testes Automatizados - · PDF fileé, então, implantada em produção. O portal começa a fazer bas- ... como no exemplo seguinte (introdução simplificada da canção “Rompendo

29

Ciclo externo: testes de aceitação com JBehave e Selenium

Quadro 1. Aplicação de exemplo: TABShare.

TABShare

e | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |B | - - - - - - - - - - - - - - - - - - - - - - - - 7 - - - 5 - 7 - 8 - 7 - 8 - 7 - 5 - 7 - 5 - 3 - - - - |G | - - 7 - 6 - 4 - 6 - 7 - - - 7 - 6 - 4 - 6 - 7 - - - 7 - - - - - - - - - - - - - - - - - - - - - 7 - - |D | - - - - - - - - - - - - 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |A | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |E | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |

e | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |B | - - - - - - - - - - - - - - - - - - - - - - - - 7 - - - 5 - 7 - 8 - 7 - 8 - 7 - 5 - 7 - 5 - 3 - - - - |G | - - 7 - 6 - 4 - 6 - 7 - - - 7 - 6 - 4 - 6 - 7 - - - 7 - - - - - - - - - - - - - - - - - - - - - 7 - - |D | - - - - - - - - - - - - 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |A | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |E | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |

O TABShare é uma aplicação na Web para compartilhamento de tablaturas mu-sicais. De maneira simplificada, uma tablatura é uma notação gráfica utilizada para representar as posições dos dedos da mão esquerda em instrumentos de cordas (ex.: violão, guitarra etc.). É uma alternativa mais simples ao uso de uma partitura. Uma tablatura pode ser facilmente representada em caracteres ASCII, como no exemplo seguinte (introdução simplificada da canção “Rompendo em fé”, de Edson e Ana Feitosa):

Dado que estou logado no sistema na página de envio de tablaturas quando envio a seguinte tablatura

e informo como título ‘Rompendo em fé’ e descrição ‘Melodia introdutória’. Então o sistema informa que a tablatura foi enviada com sucesso e as notas que a com-põem são:

D-C#-B-C#-D-A-D-C#-B-C#-D-F#-D-E-F#-G-F#-G-F#-E-F#-E-D-D

A ideia do TABShare é permitir a seus usuários o compartilhamento de tablaturas. Os usuários podem, por exemplo: enviar novas tablaturas, visualizar as notas musicais representadas por uma tablatura e avaliar a qualidade das tablaturas enviadas por outros usuários.

As tablaturas são armazenadas em uma tabela de banco de dados relacional mape-ada para uma classe correspondente por meio do Hibernate. Os atributos de uma tablatura são conteúdo, título e descrição.

Para ilustrar os ciclos de feedback interno e externo, nosso processo de desenvolvi-mento se dará da seguinte forma:1) escrevemos testes de aceitação (testes

de aceitação falhando);2) escrevemos testes de unidade (testes

de unidade falhando);3) escrevemos código de produção para

satisfazer os testes de unidade (testes de unidade passando);

4) melhoramos o código de produção por meio de refatoração (testes de unidade passando – por consequência, testes de aceitação “quase” passando);

5) escrevemos o código final da aplicação necessário (GUI etc.) para satisfazer os testes de aceitação (testes de aceitação passando).

Observação: os passos 2, 3 e 4 são execu-tados em loop, com um teste de cada vez até que todos os testes de unidade sejam concluídos. Um loop externo análogo ocorre com os testes de aceitação.

Vejamos agora a execução deste processo de desenvolvimento passo-a-passo.

De posse do cenário de uso exibido an-teriormente, já temos condições de partir para a implementação. Neste ponto, po-demos utilizar a ferramenta JBehave para expressar este cenário como um teste executável. O JBehave é um framework para desenvolvimento de software usando Behavior-driven Development (BDD), cuja ideia é expressar testes sob a forma de ce-nários escritos em linguagem natural (ele possui suporte built-in a diversas línguas, entre elas o português).

De maneira proposital, o cenário da nossa aplicação exibido anteriormente já está no formato do JBehave (o formato consiste basicamente em usar as palavras-chave “Dado que”, “Quando” e “Então”). Colocamos esta descrição num arquivo chamado envio_de_tablatura.story. Agora precisamos de uma classe Java que conterá o código que irá realizar a “tradução” do texto nas ações correspondentes sobre o sistema. A Listagem 1 exibe um exemplo desse tipo de código. O mapeamento é realizado por meio das anotações do JBehave (@Given @When e @Then – cor-respondentes aos trechos de texto com as

Page 4: Testes Automatizados - · PDF fileé, então, implantada em produção. O portal começa a fazer bas- ... como no exemplo seguinte (introdução simplificada da canção “Rompendo

30

: : www.mundoj.com.br : :

palavras-chave mencionadas). Além disso, precisamos de outra classe, exibida na Listagem 2, responsável por executar a espe-cificação. O artigo “Behaviour-Driven Development em Java na prática, com JBehave” – da MundoJ 44 – explica em detalhes a filosofia do BDD, juntamente com o JBehave. O interessante do JBehave é fazer essa ponte entre os requisitos do sistema e sua implementação, constituindo, até certo ponto, uma espécie de “documentação executável” que pode ser facilmente validada pelo cliente ou especialista de domínio.

Como temos uma aplicação Web, pode ser interessante que nossos testes de aceitação façam uso da aplicação usando sua interface gráfica no browser. Para isso, podemos usar o Selenium, ferramen-ta utilizada para realizar testes funcionais de aplicações Web. Ele pode ser instalado como plugin para o Firefox e usado para gravar num script as ações que um usuário executa numa aplicação, com opções de inserção de pontos de verificação para que se possa de-terminar se o aplicativo se comporta da maneira esperada (do ponto de vista funcional, com relação ao conteúdo das páginas exibidas). Uma vez gravado o script, o plugin oferece a funcionalidade de exportação como teste JUnit (mais sobre esta ferramenta adiante), o qual pode, então, ser integrado à suíte de testes de uma aplicação. A API do Selenium é bastante enxuta, de forma que não é nem tão complicado de escrevermos o código diretamente em Java sem precisar realizar a gravação e a exportação do teste. De fato, como programamos com o teste primeiramente, ainda nem dispomos da interface gráfica da aplicação para gravar os testes.

Quando usamos uma API como o Selenium para testar uma apli-cação através de sua interface gráfica, é bastante recomendável empregarmos o padrão Page Objects. A ideia do padrão é simples: cada página da aplicação é modelada por meio de uma classe responsável por oferecer as operações que podem ser realizadas na respectiva página. A classe que modela a página encapsula as chamadas à API do Selenium, simplificando a manutenção (pode-se reaproveitar a página em diversos testes) e tornando mais expressivos os cenários de escritos (como pode ser visto no código de teste da Listagem 1). A Listagem 3 exibe uma ideia da classe da página utilizada no teste. Neste código, já exibimos a interação que imaginamos que será feita com a página apenas para que o leitor tenha uma noção de como é a API do Selenium. Contudo, num ambiente de desenvolvimento real, o código de interação com o Selenium poderia ser adicionado às classes das páginas ao final do ciclo interno de desenvolvimento (explicado a partir da seção seguinte). Em particular, o código poderia ser ge-rado automaticamente via plugin do navegador após a confecção da interface gráfica. Para detalhes de como usar o Selenium com JBehave, conferir o artigo “Qualidade através de testes funcionais com Selenium, JBehave e Maven” da MundoJ 46.

Mesmo que não seja possível automatizar os testes funcionais de sua aplicação com Selenium, pode ser interessante adotar o JBehave para o desenvolvimento de testes de integração, obtendo-se o excelente benefício da descrição simplificada dos testes em cenários executáveis.

Listagem 1. Especificação de cenário de uso usando JBehave.

Listagem 2. Classe para executar a especificação.

public class EnvioDeTablaturaSteps { private PaginaEnvioTablatura paginaEnvio; @Given(“estou logado no sistema na página de envio de tablaturas”) public void logarNoSistema() { paginaEnvio = paginaLogin.logar(“usuario”, “senha”); } @When(“envio a seguinte tablatura $tablatura e informo” + “ como titulo $titulo e descricao $descricao”) public void envioDeTablatura(String conteudo, String titulo, String descricao) { paginaEnvio.enviarTablatura(conteudo, titulo, descricao) ; } @Then(“o sistema informa que a tablatura foi enviada com sucesso” + “ e as notas que a compõem são: %s”) public void tablaturaEnviadaComSucesso(String notas) { assertEquals(notas, paginaEnvio.getResultado()); }}

public class EnvioDeTablaturaStory { public Configuration configuration() {  return new MostUsefulConfiguration().useStoryLoader( new LoadFromClasspath(this.getClass()))  .useStoryReporterBuilder(newStoryReporterBuilder() .withDefaultFormats().withFormats(Format. CONSOLE, Format.TXT));  }      @Override public List<CandidateSteps> candidateSteps() {        return new InstanceStepsFactory(configuration(), new EnvioDeTablaturaSteps()).createCandidateSteps();     }}

Page 5: Testes Automatizados - · PDF fileé, então, implantada em produção. O portal começa a fazer bas- ... como no exemplo seguinte (introdução simplificada da canção “Rompendo

31

Listagem 3. Classe de página para envio de uma nova tabla-tura com Selenium.

Listagem 4. Testes de unidade para a classe Tablatura.

public class PaginaEnvioTablatura extends Pagina { // detalhes da API do Selenium encapsulados na classe pai public enviarTablatura((String conteudo, String titulo, String descricao) { selenium.open(“/”); selenium.type(“conteudo”, conteudo); selenium.type(“titulo”, titulo); selenium.type(“descricao”, descricao); selenium.click(“btnEnviar”); selenium.waitForPageToLoad(“30000”); assertTrue(selenium.isTextPresent(“Tablatura enviada com sucesso”)); }}

public class TablaturaTest { @Test public void getNotasOk() { String conteudo = “e|---------------------------------------------------|” + “B|------------------------7---5-7-8-7-8-7-5-7-5-3----|” + “G|--7-6-4-6-7---7-6-4-6-7---7---------------------7--|” + “D|------------7--------------------------------------|” + “A|---------------------------------------------------|” + “E|---------------------------------------------------|”; String titulo = “Rompendo em fé”; String descricao = “Melodia introdutória”; Tablatura tablatura = new Tablatura(conteudo, titulo, descricao); assertEquals(“D-C#-B-C#-D-A-D-C#-B-C#-D-F#-D-E-F#-G-F#-G-F#-E- F#E-D-D- “, tablatura.getNotas()); } // outros métodos de testes exercitando diferentes cenários}

Ao executar o código da Listagem 2 (usando o JUnit, mais sobre esta ferramenta adiante), o JBehave nos informará que os três pas-sos da especificação estão pendentes. Esse comportamento é ób-vio, visto que não temos nada implementado. De qualquer forma, com este teste em mãos, temos um critério de avaliação quanto ao progresso da implementação da funcionalidade do ponto de vista do usuário externo. Agora, é hora de entrarmos no ciclo interno de desenvolvimento da funcionalidade usando TDD.

Testes de unidade com JUnit

O TDD está baseado no ciclo red-green-refactor, outra forma de se referir ao ciclo teste-codificação-design, que explicamos ante-riormente, usando como referência as cores da barra de resultado exibida por ferramentas de execução de testes de unidade, como o JUnit. Quando algum teste falha, é exibida uma barra verme-lha; quando todos os testes passam é exibida uma barra verde. A Listagem 3 exibe um trecho de código com um primeiro teste de unidade para o sistema. A função do teste, expressa pelo seu nome, é verificar a corretude do método getNotas(). Aqui usamos a ferramenta JUnit para escrever os testes automatizados.

O JUnit é extremamente popular, já vem com as principais IDEs Java (Eclipse, NetBeans etc.) e é bastante simples de usar. Basta criar uma classe Java comum e anotar os métodos que devem ser executados como testes com a anotação @Test. Tendo um méto-do com esta anotação, tudo o que é necessário fazer é escrever o código que exercitará a funcionalidade desejada e, então, es-pecificarmos as assertivas desejadas. No código da Listagem 4, criamos uma nova tablatura, chamando o método getNotas() e verificando se o resultado que ele retorna é igual à string com as notas esperadas.

Você pode estar se perguntando onde está a classe Tablatura. A resposta: ela ainda não existe! Estamos escrevendo primeiramente o teste, fazendo o que é chamado de programação por intenção, que consiste em escrevermos um código da forma como gostarí-amos de usá-lo antes mesmo de ele existir (este é o pilar central do TDD). Escrever código assim nos força a refletirmos sobre o design do sistema, focando nas suas interfaces e na facilidade de

uso, isto é, no “quê” deve ser feito e não no “como” fazer. Esse processo fatalmente nos leva a designs menos acoplados, com interfaces que expressam de forma mais clara a sua intenção.

O próximo passo é fazer o código compilar por meio da criação da classe Tablatura com o método getNotas() (o Eclipse pode ser usado para fazer isso automaticamente, basta pressionar <Ctrl> + 1 seguido de <Enter> na linha com erro de compilação para que ele gere o código necessário [com implementação vazia]). Com o código compilando, podemos executar o teste pelo Eclipse e ver o teste falhar (barra vermelha!), pois o método getNotas() ainda não faz nada (simplesmente retorna null). Com isso, já se pode implementar de fato o método getNotas() da maneira mais sim-ples com a qual se consiga satisfazer ao teste que está falhando. Com isso feito corretamente, o teste deverá passar (barra verde!). Agora, com o teste passando, procedemos ao último passo do ci-clo do TDD, removendo duplicação e tornando o código o melhor possível (refatoração!).

Page 6: Testes Automatizados - · PDF fileé, então, implantada em produção. O portal começa a fazer bas- ... como no exemplo seguinte (introdução simplificada da canção “Rompendo

32

: : www.mundoj.com.br : :

Ao longo desse processo, novos cenários de testes deverão ser implementados para o método getNotas() bem como outros testes para os demais métodos que se façam necessários para a classe Tablatura. Após esse processo, vamos assumir que chegamos ao código da Listagem 5.

Agora, olhando a implementação do método getNotas(), podemos notar que existe a instanciação de um objeto do tipo Tablatura-Parser, responsável por fornecer quais são as casas pressionadas no braço do violão dado um conteúdo de tablatura (deixamos o código para TablaturaParser como exercício para o leitor). Sendo assim, o leitor pode perceber que quando escrevemos um teste para o método getNotas(), o teste também estará testando métodos da classe TablaturaParser e, portanto, deixou de ser um teste de unidade (cujo objetivo é testar métodos de uma classe isoladamente). Bom, aqui, neste exemplo didático, talvez não haja grandes problemas nisso. Porém, se a classe colaboradora (neste caso, TablaturaParser) fosse uma classe de implementação custo-sa, difícil de instanciar ou dependesse de recursos externos que não estivessem sob fácil controle (ex.: banco de dados, rede etc.) seria bastante difícil escrever um simples teste de unidade. Por isso, o recomendável é utilizarmos injeção de dependência para que possamos substituir os objetos colaboradores por objetos que possam tornar os testes de unidade possíveis. A Listagem 6 exibe o código da Tablatura usando injeção de dependências para o parser colaborador.

Para uma boa introdução ao desenvolvimento com testes automa-tizados e JUnit, conferir o artigo “Testes Unitários para Camadas de Negócios no Mundo Real.“ – MundoJ 23.

Listagem 5. Código de produção para a classe Tablatura.

Listagem 6. Refatoração da classe Tablatura para usar injeção de dependência.

public class Tablatura {

private String conteudo;

private String titulo;

private String descricao;

public Tablatura(String conteudo, String titulo, String descricao) {

this.conteudo = conteudo;

this.titulo = titulo;

this.descricao = descricao;

}

public String getNotas() {

StringBuilder resultado = new StringBuilder();

TablaturaParser parser = new TablaturaParserImpl();

List<Casa> casas = parser.parse(descricao);

for (Casa casa : casas) {

int numero = casa.getNumero();

String corda = casa.getCorda();

// ... restante do algoritmo para encontrar a nota...

resultado.append(nota);

resultado.append(“-”);

}

return resultado;

}

}

public class Tablatura { // ….. private TablaturaParser parser; public Tablatura(String conteudo, String titulo, String descricao, TablaturaParser parser) { //... this.parser = parser; } // restante do código igual sem a instanciação da dependência}

Mock objects com Mockito

Testes dos DAOs com DbUnit e HSQLDb

Após a modificação da classe Tablatura para usar injeção de de-pendência, devemos modificar o teste de unidade para passarmos o parser exigido (na verdade, essa modificação seria feita primei-ramente no teste e depois propagada para o código – mas vamos seguir nesta sequência didática).

Assumindo que programamos por intenção (escrevendo o teste primeiro, pensando no código que gostaríamos de usar), neste ponto nós teríamos condições apenas de derivar a interface Tabla-turaParser (ainda não temos sua implementação). Para mantermos nosso foco na classe sendo testada unitariamente, podemos gerar uma implementação fake (um stub) para usarmos nos testes ou fazer uso de um framework de mock objects. Mock objects são “objetos falsos” que normalmente são utilizados em testes auto-matizados para substituir objetos colaboradores. Diferenciam-se de simples stubs por possuírem funcionalidades de configuração de quais chamadas de método devem ser realizadas no mock, quantas vezes, com quais argumentos e quais valores de retorno, entre outras coisas.

Existem diversos frameworks de mock objects para Java, sendo Mockito e EasyMock os mais populares. A Listagem 7 exibe o código do teste refatorado para usar o Mockito e considerando a injeção de dependência de TablaturaParser. Neste código, uti-lizamos um método de setup anotado com @Before, anotação do JUnit para indicar que o referido método deve ser executado ime-diatamente antes da execução de cada método de teste (também existem as anotações @After, @BeforeClass e @AfterClass). Este setup é necessário para que o Mockito faça a criação dos mocks para todos os objetos anotados com @Mock.

Para mais detalhes sobre o ciclo do TDD com JUnit e Mockito, conferir o artigo “Evolução do Design através de Testes e o TDD” – da MundoJ 41; e para uma boa explicação sobre a diferença de mocks e stubs, ver o artigo “Mocks aren’t stubs”, de Martin Fowler.

Outra ferramenta também importante no cenário de testes auto-matizados com Java é o DbUnit, cujo objetivo é facilitar o geren-ciamento do estado de um banco de dados entre as execuções dos testes. Com ele, podemos especificar em arquivos XML quais os dados que queremos que sejam inseridos em quais tabelas e

Page 7: Testes Automatizados - · PDF fileé, então, implantada em produção. O portal começa a fazer bas- ... como no exemplo seguinte (introdução simplificada da canção “Rompendo

33

Listagem 7. Teste de unidade usando mock object. Listagem 8. Teste de integração de DAO usando DbUnit.

Listagem 9. Exemplo de arquivo com dados de testes para DbUnit.

public class TablaturaMockito { @Mock private TablaturaParser parser; @Before public void setUp() { // instancia os mocks para os campos anotados com @Mock MockitoAnnotations.initMocks(this); } @Test public void getNotasOk() { // .. igual ao codigo anterior… // when(parser.parse(conteudo)).thenReturn(Arrays.asList(new Casa(7, “G”), new Casa(6, “G”), new Casa(4, “G”) /*... demais casas do conteudo... */)); Tablatura tablatura = new Tablatura(conteudo, titulo, descricao, parser); assertEquals(“D-C#-B-C#-D-A-D-C#-B-C#-D-F#-D-E-F#-G-F#-G-F#-E-F#- E-D-D-”, tablatura.getNotas()); verify(parser).parse(conteudo)); }}

public class TablaturaDAOTest { private TablaturaDAO dao; @Before public void setUp() { Session session = setUpDb(); dao = new TablaturaDAOImpl(session); } private Session setUpDb() { Class.forName(“org.hsqldb.jdbcDriver”); Connection jdbcConnection = DriverManager.getConnection( “ jdbc:hsqldb:mem:testdb “, “sa”, “”); IDatabaseConnection connection = new DatabaseConnection( jdbcConnection); IDataSet dataSet = new FlatXmlDataSet(new File( “dados_tablatura_dao_test.xml”)); DatabaseOperation.CLEAN_INSERT.execute(connection, dataSet); // … codigo para obter session do Hibernate…. } @Test public void getObterTablaturasPorTom() { List<Tablatura> tablaturas = dao.obterTablaturasPorTom(“G”); // realizar os devidos asserts baseado no conteúdo do banco de dados }}

<?xml version=’1.0’ encoding=’UTF-8’?>

<dataset>

<tablatura id=”1” titulo=”Musica X” descrição=”Intro X” conteudo=”7-6-2-7.....” />

<tablatura id=”2” titulo=”Musica Y” descrição=”Intro Y” conteudo=”1-3-2-1.....” />

<tablatura id=”3” titulo=”Musica Z” descrição=”Intro Z” conteudo=”4-2-1-9.....” />

</dataset>

ele, então, se encarrega de fazer as devidas atualizações de forma que o estado do banco fique sempre consistente antes ou depois de cada execução de teste. O DbUnit é uma ferramenta que se mostra bastante útil para realizarmos testes em classes DAO (Data Access Object), que são normalmente as classes que acomodam a funcionalidade de acesso a bancos de dados numa aplicação.

Testes que envolvem recursos externos, como bancos de dados, também costumam ser bem mais lentos. Para reduzir o overhead nesses tipos de testes, recomendamos o uso do HSQLDb, um sistema de banco de dados mais leve, escrito em Java e que supor-ta execução em memória. Cabe ressaltar, no entanto, que ainda assim é bastante recomendável que haja outro nível de testes que utilize o sistema de banco de dados o mais próximo possível do que será utilizado em produção. Em nossa aplicação de exemplo, utilizaríamos um banco de dados igual ao real na execução dos testes de aceitação com o JBehave, por exemplo.

A Listagem 8 exibe um exemplo do que poderia ser um teste para o nosso DAO de tablaturas, enquanto a Listagem 9 exibe o XML com os dados a serem inseridos/removidos pelo DbUnit (apenas para que o leitor tenha uma noção). Como pode ser visto, o uso do DbUnit consiste em especificar em arquivo XML os dados a serem populados nas tabelas do banco5, a criação de um objeto DataSet com os dados do XML e a instrução de carga do objeto DataSet no banco de dados HSQLDb em memória no setup do teste. Em código real, normalmente encapsulamos a configuração do DbUnit em uma classe utilitária e a reaproveitamos ao longo dos testes automatizados. O artigo “Testes de Unidade para Cama-das de Persistência no Mundo Real” – da MundoJ 24 – trata em detalhes o uso do DbUnit para testes de DAOs.

Com os testes de unidade e integração passando, completamos um ciclo interno de feedback. Está na hora de concluirmos a fun-cionalidade, satisfazendo o teste de aceitação.

5As tabelas para o banco de teste HSQLDb normalmente são automaticamen-

te geradas pelo Hibernate por meio do uso do parâmetro hibernate.hbm2ddl.

auto=true quando a EntityManagerFactory é criada. Para tabelas não mapeadas,

a criação da tabela deve ser feita manualmente antes de o DbUnit tentar inserir

os dados.6Evidentemente, cada historia do sistema seria implementada com diversos testes

de aceitação e de unidade, com diferentes cenários e não apenas com um único

teste como exibimos aqui por propósitos didáticos.

Concluindo o ciclo externo de testes

Após os diversos ciclos internos de TDD (red-green-refactor), estamos prestes a atender nosso teste de aceitação, mostrado no início do artigo, na Listagem 1. Para isso, ainda se torna necessária a criação da interface gráfica da aplicação (GUI) e a configuração correta do sistema de banco de dados semelhante ao utilizado em produção. Tendo feito isso, estamos prontos para executar o teste de aceitação e recebermos a barra verde confirmando o sucesso da implementação6.

Page 8: Testes Automatizados - · PDF fileé, então, implantada em produção. O portal começa a fazer bas- ... como no exemplo seguinte (introdução simplificada da canção “Rompendo

34

: : www.mundoj.com.br : :

Testes no mundo real

Tendo mostrado esta forma interessante de se desenvolver softwa-re com testes, vamos voltar agora ao mundo real. “Como assim?!” – você pergunta. “Você me faz ler este artigo até aqui para dizer que a realidade é outra?”. Calma, amigo leitor. Esta seção é mais para que você se conscientize do fato de que no seu ambiente pode não ser possível aplicar as coisas da “maneira ideal” (se é que isso existe em desenvolvimento de software).

Em projetos reais, nem sempre é possível ou factível aplicarmos na íntegra o processo de desenvolvimento com testes que apre-sentamos neste artigo (com ambos os ciclos de feeback externo e interno mencionados). Pessoas inexperientes na técnica, proces-sos de desenvolvimento burocráticos, pressões de prazo7, o tipo de sistema sendo desenvolvido (de repente, pode ser um sistema legado) ou da plataforma sendo utilizada (alguém aí desenvolve em COBOL ou NATURAL?) são todos fatores que podem repre-sentar empecilhos a esse modelo de desenvolvimento. Cabe a você adaptá-lo para a sua realidade de forma a obter o máximo dos benefícios.

De qualquer forma, se o seu ambiente possui as ferramentas ne-cessárias, encorajamos fortemente o leitor para que se esforce para utilizar testes de unidade e integração (envolvendo mais de uma classe, com banco de dados etc.), seja programando estritamente com o teste primeiro ou não (principalmente, se o leitor for inex-periente na técnica). Contudo, aconselhamos que os testes sejam sempre escritos juntamente (imediatamente antes, de preferência, ou imediatamente depois) com o código de produção para que haja os benefícios de feedback instantâneo e localizado, além de código menos acoplado. Sempre programe com o teste em mente, esteja ele já materializado em código como um teste falhando ou não. Por vezes, antes de começar a escrever os testes de unida-de, pode ser útil rabiscar alguma ideia do design do sistema. É importante ressaltar que não se trata de fazer BDUF (Big Design Up-front), mas sim apenas de se ter uma noção da estrutura inicial do sistema para ajudar a escrever os primeiros testes.

Alguns desenvolvedores só escrevem testes de integração auto-matizados depois de implementarem a funcionalidade para terem

o benefício dos testes de regressão. Esta abordagem possui pelo menos dois problemas: 1) perda de feedback instantâneo e locali-zado; 2) baixa qualidade em design (i.e. alto acoplamento e baixa coesão). A perda de feedback localizado é evidente, pois quando ocorrem erros em testes de integração, existem diversos pontos a se analisar, além de o feedback ser tardio, gerando maior tempo de correção pela necessidade de se recuperar o contexto. A baixa qualidade em design se manifesta, pois não existe a preocupação em tornar o código testável isoladamente, acarretando em acopla-mentos indesejados no sistema.

Também é importante estar atento na qualidade dos testes produ-zidos, para que não seja gerada uma “falsa ilusão” de segurança. Normalmente, a cada método não trivial de código de produção corresponderá mais de um método de teste de unidade, exercitan-do diferentes cenários. Além disso, é imprescindível que os testes sejam escritos com a mesma qualidade que o código de produção (evitar duplicação de código!), pois eles terão que ser mantidos, juntamente com o restante do código da aplicação.

Vale a pena mencionar uma última ferramenta que pode ser útil para quem não faz uso estrito de TDD ou está no processo de automatização de testes de código legado: o EclEmma. Esta é uma ferramenta de análise de cobertura de testes. Está disponível para ser utilizada diretamente no Eclipse via JUnit e também para ser integrada ao build da aplicação via Ant. A ferramenta indica quais trechos de código de produção estão sendo executados por algum teste, fornecendo um relatório da porcentagem do código coberto por testes. Uma ferramenta deste tipo pode ser útil para derivar novos casos de testes e também servir como uma métrica (bas-tante limitada, aliás, mas melhor que nada) de como andam os testes automatizados numa aplicação. 100% ou 90% de cobertura

7A noção inicial que se tem é que o tempo necessário para desenvolver uma fun-

cionalidade com testes automatizados é pelo menos o dobro do que fazê-lo sem

os testes. Porém, considerando-se o ciclo de desenvolvimento como um todo,

compensa-se esse custo extra inicial com a redução de defeitos e, quando estes são

encontrados, com a redução do tempo necessário para corrigi-los (fora os diversos

outros benefícios citados neste texto).

Page 9: Testes Automatizados - · PDF fileé, então, implantada em produção. O portal começa a fazer bas- ... como no exemplo seguinte (introdução simplificada da canção “Rompendo

35

Para Saber Mais

-do Real.” – MJ 23

Mundo Real” – MJ 24

DBUnit e HSQLDB” – MJ 38

41

código legado” – MJ 43

com JBehave” – MJ 44-

nium, JBehave e Maven” – MJ 46

– Steve Freeman e Nat Pryce

Java Developers” – Lasse Koskela

Hunt e Dave Thomas

GUJ – Discussões sobre o tema do artigo e assuntos relacionados

Discuta este artigo com 100 mil outros desenvolvedores em www.guj.com.br/MundoJ

Referências

e testes é irreal e desnecessário (às vezes, até prejudicial) para a maioria dos projetos e o leitor deve se policiar para realmente escrever testes que agreguem valor e não simplesmente para au-mentar a porcentagem de cobertura.

Por fim, não podemos deixar de mencionar que é bastante de-sejável que a equipe de desenvolvimento utilize um build au-tomatizado para compilação e execução de testes num servidor de integração contínua para que todo o código integrado seja testado continuamente. À medida que o número de testes cresce numa aplicação, pode ficar inviável executar todos os testes de mais alto nível (aceitação ou integração) na máquina local antes de se realizar o commit no repositório de fontes. Nestes casos, é comum o desenvolvedor executar um subconjunto dos testes em sua máquina e deixar para o servidor de integração contínua rodar os testes mais pesados no sistema inteiro. Como uma úl-tima observação sobre o assunto, mais importante até do que a ferramenta é a prática de integrar código frequentemente. Muitos aconselham pelo menos um commit no repositório diariamente (muitos desenvolvedores ainda possuem uma resistência quanto a seguir essa recomendação e o desenvolvimento em pequenos pas-sos estimulado pelo TDD é de grande valia para ajudar nisso). Os commits frequentes diminuem o esforço de integração, facilitando a comunicação da equipe.

Considerações finais

Testes automatizados de software é um assunto extremamente vasto e o propósito aqui neste artigo foi apenas fornecer uma visão geral sobre o assunto, dando ao leitor uma ideia dos principais tipos de ferramentas usadas num processo de desenvolvimento de software orientado a testes. Incentivamos o leitor a que estude a bibliografia de livros e artigos indicados e, principalmente, es-force-se para aplicar as técnicas no seu trabalho diário. No início, será difícil e poderá até parecer um pouco contraproducente, mas, ao longo do tempo, certamente será perceptível a diferença para a qualidade geral do projeto, considerando características externas e internas de qualidade. É tudo uma questão de conhecimento, prática e, principalmente, disciplina.

“Determinando tu algum negócio, ser-te-á firme, e a luz brilhará em teus caminhos.” (Jó 22:28)