testes_
T
estes automatizados constituem uma ferramenta indispensável a todo bom desenvolvedor de sof-tware. Feedback instantâneo e localizado, rede de proteção contra bugs, suporte a refatoração e melho-ria contínua no design são apenas alguns dos muitos benefícios que podemos citar para um sistema cons-truído com testes.Como em toda atividade de desenvolvimento, aprender a escrever testes automatizados com qua-lidade para as diversas partes que compõem um sis-tema de software é algo que leva tempo e bastante estudo. Neste artigo, apresentamos ao leitor (prin-cipalmente àquele que ainda é iniciante no assunto) um conjunto de dicas que podem ser úteis na criação de testes automatizados para um sistema. As dicas são apresentadas dentro do contexto da plataforma Java, mas são igualmente proveitosas em qualquer outra linguagem ou ambiente de desenvolvimento. Distribuímos as dicas em quatro seções distintas, a saber: 1) Construção (como criar bons testes), 2) Tes-tabilidade (como tornar o software testável), 3) Além dos testes de unidade (dicas relacionadas a testes de integração) e Organização (como organizar adequa-damente o código de teste).
Construção
Nesta seção, começamos com um conjunto de
dicas relacionadas à construção de testes automati-zados.
Dica #1: lembre-se do acrônimo ATRIP1
(bons testes são “uma viagem”). Isto
é, devem ser automáticos (automatic),
completos (through), repetíveis
(repea-table), independentes (independent) e
profi ssionais (professional).
A primeira característica é óbvia, visto que esta-mos tratando de testes automatizados neste artigo. Tanto a invocação dos testes quanto a verifi cação dos resultados devem ser executadas automaticamente para que os principais benefícios sejam alcançados.
Os testes devem ser completos, ou seja, devem testar todo o código de produção escrito. Ferramen-tas que analisam a cobertura de código (ou cober-tura de testes), tais como o Cobercober-tura e o EclEmma são bastante úteis para ajudar a identifi car trechos de código que não possuem testes. Neste ponto, vale chamar a atenção para o bom senso em relação à co-bertura de código. 100% de coco-bertura de código, na
1. Do livro “Pragmatic Unit Testing with JUnit”, Andy
Hunt e Dave Thomas.
Dicas
efi cazes para a
criação
Alexandre Gazola | [email protected] | @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, possuindo 10 anos de experiência com desenvolvimento na plataforma Java e as certifi cações SCJP e SCWCD. É articulista e editor técnico da revista MundoJ e também possui um blog em http://alexandregazola.wordpress.com. Nas horas vagas, gosta de ler, estudar idiomas e tocar violão.
Escrever testes automatizados faz parte do dia a dia de todo
desen-volvedor de software preocupado com a qualidade de seu trabalho.
E, como toda disciplina de engenharia de software, essa é uma
ta-refa que possui seus desafios, sutilezas e complexidades. Neste
arti-go, o objetivo é fornecer ao leitor um conjunto de dicas que possam
auxiliar na criação de testes automatizados de maneira mais eficaz.
maioria das vezes, não é necessário e nem factível.Em muitos casos, não se ganha muito em escrever testes de unidade para setters e getters, métodos
wra-pper (que não fazem nada além de delegar a tarefa
para um terceiro), fábricas, casos de exceções impro-váveis (mas que o Java força o tratamento), apenas para mencionar alguns casos mais comuns. Cabe ao desenvolvedor avaliar a real necessidade. Além dis-so, cuidado para não confiar demais numa cobertura de testes elevada, pois, mesmo nesse caso, podem existir cenários importantes de execução que ainda não estejam sendo contemplados. Dito isto, aprovei-to para encorajar a prática do Test-Driven
Develop-ment, cujo princípio básico garante, por definição,
um código com elevada cobertura, pois nenhum có-digo de produção pode ser criado sem que haja um teste falhando que o motive (ver dica #3).
Bons testes também devem ser repetíveis e in-dependentes, ou seja, deve ser possível executar um teste de forma isolada dos outros, inúmeras vezes, obtendo sempre os mesmos resultados. Dessa forma, torna-se possível, caso seja encontrada alguma falha no sistema, isolar o problema por meio dos testes que apresentarem falhas. Tem-se o ótimo benefício do feedback localizado. Nunca faça um teste automa-tizado depender da saída de outro teste automatiza-do! Às vezes, as pessoas são tentadas a adotar essa
de testes
automatizados
estratégia, pois, em tese, seria algo que facilitaria o
setup dos testes (ocorre o aproveitamento das saídas
de testes anteriores). Mais adiante no artigo vere-mos dicas para ajudar nessa tarefa de código de setup para os testes.
Por fim, é vital destacar que bons testes devem ser profissionais. Isso significa que devem ser criados com os mesmos padrões de qualidade que o código de produção. Num sistema desenvolvido com testes, cerca de metade do código a ser mantido será consti-tuído de código de testes, então, é muito importante observar sempre as boas práticas: alta coesão, baixo acoplamento, boa legibilidade etc.
Dica #2: Crie testes pequenos, focados
e com bons nomes.
Como mencionamos, o princípio da alta coesão também se aplica ao código de testes. Normalmente, um método de teste deve exercitar uma única carac-terística do respectivo método de produção. Dessa forma, o código de teste ficará reduzido, focado em apenas uma tarefa e, portanto, será mais fácil de en-tender e manter. Quando acontecer uma falha, será trivial delimitar o local do problema. O nome do método de teste também é importantíssimo e deve refletir exatamente o que está sendo testado. Não
economize nas palavras! Preste atenção no nome dado ao teste. Se o mesmo possuir conjunções (i.e., e/ou), pode ser um sinal de que coisas demais estão sendo testadas. Seguindo esse princípio, na maioria das vezes, haverá vários métodos de teste para cada método de produção, cada qual exercitando um cená-rio diferente.
É comum novos casos de testes virem à mente à medida que um teste está sendo escrito. Anote-os numa lista separada para implementação posterior. Comece também pelo teste mais simples, de forma que o código possa evoluir aos poucos. Também é im-portante verificar o valor que cada caso de teste está agregando e se não estão ocorrendo sobreposições entre testes (vários testes testando a mesma coisa).
Alguns desenvolvedores gostam de seguir a re-gra de colocar apenas uma assertiva por método de teste, pois dizem que mais de uma pode significar que o método está fazendo coisas em excesso. Eu, particularmente, acho exagerado seguir essa regra de forma estrita. Muitas vezes, faz sentido ter mais de um assert, especialmente em casos em que se dese-ja verificar vários objetos de coleções, mais de uma propriedade de objetos retornados e coisas do tipo. O importante é que o teste seja coeso!
Dica #3: Use TDD sempre que possível
TDD (Test-driven Development, “Desenvolvimen-to orientado a testes”) é uma técnica que consiste em desenvolver uma funcionalidade primeiramen-te escrevendo um primeiramen-tesprimeiramen-te automatizado para só então implementar o código que deverá satisfazer ao teste. Uma vez que o teste esteja passando, procedemos ao trabalho de melhorar o código produzido (i.e. refa-toração), visto que já dispomos de um mecanismo de proteção que fornece a devida segurança. Nessa maneira de se desenvolver, em vez de aplicar o tradi-cional ciclo design-codificação-testes, ao receber uma nova funcionalidade, aplicamos
testes-codificação--design! Escrever o teste antes, prática conhecida
como test-first, obriga que o código produzido seja testável por construção, e código testável implica có-digo com baixo acoplamento, aspecto extremamente importante em qualquer bom design de software. Eis alguns outros benefícios de uma abordagem TDD:
» Melhor compreensão do problema. » Melhor foco na tarefa a ser feita.
» Aprendizado mais rápido (feedback instantâ-neo!).
» Menos esforço de retrabalho.
Mesmo que o leitor não use TDD de forma muito rigorosa, é bastante benéfico o simples fato de se al-ternar com frequência entre código de testes e código
de produção. Para saber mais sobre o que dizem
al-guns estudos empíricos sobre os benefícios e
vanta-gens de se usar TDD, ver o artigo “Benefícios do TDD Medidos na Prática”, da MJ 54.
Dica #4: Refatore o código de testes
Refatoração contínua é um pilar para a manu-tenção do bom estado do sistema, e isso inclui o pró-prio código de testes! Depois de obter a barra verde e refatorar o código de produção, veja também se o código dos testes está legível, fácil de entender e manter. Elimine os maus cheiros encontrados. Uma boa parte do esforço relacionado aos testes está no código de setup para a execução do teste desejado. Uma boa dica para eliminar duplicação e aumentar a legibilidade dos testes consiste em usar builders (ver dica seguinte).
Vale aqui uma observação em relação à refatora-ção no código de testes. Um objetivo primário da refa-toração é eliminar a duplicação de código (Princípio da ortogonalidade ou DRY – Don’t Repeat Yourself). Porém, muitas vezes é admissível existir um pouco de duplicação para favorecer a legibilidade do código (alguns chamam isso de princípio DAMP – Descrip-tive and Meaningful Phrases2). É interessante que, em termos de entendimento, cada método de teste seja o mais autocontido possível. Deve ser fácil ler um método de teste e entender a configuração do cená-rio, execução e verificação. Para atingir esse objetivo, é comum existir um pequeno nível de repetição nos testes.
Para ficar bem claro: o código dos testes é a me-lhor fonte de documentação do uso do sistema, en-tão é vital que ele seja legível! Uma ferramenta in-teressante é o Hamcrest, um framework que permite a criação declarativa de regras para a realização de validações. Com ele, pode-se escrever algo como:
assertThat(objeto1, equalTo(objeto2));
Além de melhorar a legibilidade do teste, tam-bém é possível ter um controle mais fino dos valo-res que estão sendo efetivamente verificados (quais propriedades de quais objetos), o que torna os testes mais robustos quando mudanças pequenas não rela-cionadas são realizadas em código.
Dica #5: Use builders para melhorar a
legibilidade e redigibilidade dos testes
Em testes de unidade, é comum a necessidade de instanciação de uma cadeia de objetos de domínio, especialmente no código de setup do teste. Como ilustração, consideremos um sistema de simulação para uma concessionária de carros (ver Listagem 1). Na Listagem 2, encontramos um exemplo de código
para testar o método vender() da classe Concessiona-ria. Note que só o código de setup necessário para a criação de um objeto Carro para uso no teste já ocupa mais da metade do teste! Situações como essas, que exigem a construção de objetos complexos, são ideais para aplicação do padrão de projeto Builder.
Listagem 1.
Código de produção.publicclass Concessionaria { publicvoidvender(Carro carro) { // lógica de negócio...
} }
class Carro {
private String placa; private String chassi; private Motor motor; private List<Roda> rodas; // outros métodos omitidos ...
}
Listagem 2
. Código de teste para a classe Concessio-naria.publicclass ConcessionariaTest { @Test
publicvoidvender() { // código de setup
Motor motor = newMotor(newCarter(), newBloco(), newCabecote());
Placa placa = newPlaca(“RPP - 1989”); Chassi chassi =
newChassi(“9BGJG19HWWB564200”); List<Roda> rodas = Arrays.asList(newRoda(15), newRoda(15), newRoda(15), newRoda(15)); Carro carro = newCarro(placa, chassi, motor, rodas); Concessionaria concessionaria =
newConcessionaria(); // execução
concessionaria.vender(carro);
// verificação dos resultados obtidos com asserts...
} }
A Listagem 3 exibe um exemplo de código possí-vel de builder para construir objetos Carro. A técnica é muito simples. Basta criar métodos para armazenar o valor de cada propriedade, expondo ao cliente apenas a configuração necessária para os testes e esconden-do os detalhes não muito relevantes. Cada métoesconden-do deve guardar o valor passado e retornar o próprio ob-jeto da classe (return this) para simplificar o código cliente (que pode, assim, fazer chamadas encadeadas – padrão Fluent Interface). Uma vez satisfeito com a configuração realizada, o cliente faz uma chamada ao método build() para que o objeto seja, de fato, cons-truído. A Listagem 4 mostra o código de teste refato-rado para utilizar o builder.
Listagem 3.
Builder para objetos da classe Carro. publicclass CarroBuilder {
private String numeroPlaca; private String numeroChassi;
publicstatic CarroBuilder carro() { returnnewCarroBuilder(); }
public CarroBuilder placa(String numeroPlaca) { this.numeroPlaca = numeroPlaca;
returnthis; }
public CarroBuilder chassi(String numeroChassi) { this.numeroChassi = numeroChassi;
returnthis; }
public Carro build() {
Motor motor = newMotor(newCarter(), newBloco(), newCabecote()); Placa placa = newPlaca(numeroPlaca); Chassi chassi = newChassi(numeroChassi); List<Roda> rodas = Arrays.asList(newRoda(15), newRoda(15), newRoda(15), newRoda(15)); returnnewCarro(placa, chassi, motor, rodas); }
}
Listagem 4.
Código de teste refatorado para usar o builder.@Test
publicvoidvenderComBuilder() { // código de setup com builder
Carro carro = carro().placa(“RPP - 1989”). chassi(“9BGJG19HWWB564200”).build();
Concessionaria concessionaria = newConcessionaria(); // execução
concessionaria.vender(carro); // verificação dos resultados obtidos...
}
Dica #6: Use integração contínua
É importantíssimo que seja utilizado um build automatizado para compilação e execução dos testes num servidor de integração contínua. À medida que o número de testes aumenta, ficará inviável executar todos os testes (incluindo os de integração e aceita-ção) na máquina local antes de cada commit no repo-sitório de fontes. Nesse caso, é recomendável que o desenvolvedor execute todos os testes de unidade e apenas um subconjunto dos testes de integração em sua máquina, deixando a execução completa para o servidor de integração. Vale lembrar que não basta ter um servidor de integração contínua, é necessário
aplicar a disciplina de realizar updates e commits fre-quentemente (no mínimo, uma vez por dia).
Testabilidade
As próximas dicas podem ajudar a tornar o códi-go de produção mais testável.
Dica #7: Use Injeção de Dependência
Sabemos que, num sistema de software orienta-do a objetos, existem diversas classes, as quais cola-boram umas com as outras para realizar uma tarefa. Essa colaboração se verifica em relacionamentos de dependência, tais como associações, agregações e composições.
Por exemplo, podemos ter uma classe que realiza determinada lógica de negócio e necessita interagir com outra classe que realiza acesso a bancos de dados, como ilustra a Listagem 5. No exemplo da listagem, a própria classe ClienteService está se encarregando de obter uma dependência da qual ela necessita, e faz isso por meio de uma chamada a método estático de outra classe (uma fábrica). Essa chamada estática (ou, ainda que o próprio método realizasse a instanciação do DAO via operador new) torna bastante difícil a es-crita de um teste de unidade para esse método, pois existe uma dependência concreta da classe Cliente-Service em relação à classe ClienteDAO. Nesse caso, a situação é ainda mais problemática, visto que a classe ClienteDAO realiza acessos a banco de dados, o que exigiria ainda mais código de setup para o teste auto-matizado (que a essa altura já teria deixado de ser um teste de unidade há muito tempo!).
Listagem 5.
Classe de produção com código acopla-do e difícil de testar.publicclass ClienteService {
publicbooleancadastrarNovoCliente(Cliente cliente) { if (cliente.cumpreAsCondicoesNecessarias()) { // ... mais logica aqui ...
ClienteDAO clienteDAO = ClienteDAOFactory. getClienteDAO(); // não faça isto!
clienteDAO.salvar(cliente); returntrue; } else { // lógica... returnfalse; } } }
A dica, portanto, é utilizar a técnica de injeção de dependências (DI). Explicando de maneira sim-ples, isso significa que TODOS os objetos colabora-dores necessários devem ser passados para a classe em questão, seja via construtor (que é a forma mais
indicada) seja através de métodos set. A Listagem 6 exibe um exemplo de código que faz uso de injeção de dependência por meio do construtor.
Listagem 6.
Classe de produção com injeção de dependência.publicclass ClienteService {
private ClienteDAO clienteDAO;
publicClienteService(ClienteDAO clienteDAO) { this.clienteDAO = clienteDAO;
}
publicbooleancadastrarNovoCliente(Cliente cliente) { if (cliente.cumpreAsCondicoesNecessarias()) { // ... mais logica aqui ...
clienteDAO.salvar(cliente); } else {
returnfalse; }
} }
Dica #8: Use mocks para isolar o objeto
sob teste das demais dependências
Uma vez que sigamos a boa prática de injeção de dependências no código de produção, será bem mais simples escrever os testes de unidade, pois as dependências passadas para as classes sob teste não precisarão ser “objetos reais”, podendo ser simples-mente mock objects (ou, simplessimples-mente, mocks, obje-tos “fake”). Razões para o uso de mocks incluem: criar objetos cujo estado é difícil de reproduzir, criar ob-jetos que são lentos (ex.: obob-jetos que fazem acesso a bancos de dados) ou mesmo criar objetos cuja im-plementação ainda não exista (caso em que se tenha apenas a interface, por exemplo).
Existem diversos frameworks disponíveis. A maioria deles permite que o desenvolvedor especi-fique, entre outras coisas, quais métodos devem ser executados, com quais parâmetros, qual retorno, ex-ceções que devam ser lançadas, ordem etc. Na comu-nidade Java, os frameworks mais populares são Mo-ckito e EasyMock, ambos já abordados nesta revista em diferentes artigos.
Como observação, vale lembrar que normalmente não se usa mocks para objetos POJO (aquelas classes mais simples, que muitas vezes não possuem nada, exceto métodos get e set). Nesse caso, costuma-se usar os objetos reais mesmo, construídos por meios de builders (ver dica #5).
Dica #9: Evite colocar lógica nos
cons-trutores
Para ter maior testabilidade, é bastante recomen-dável que os construtores das classes não façam nada mais do que atribuir as dependências e parâmetros recebidos a variáveis de instância. Dessa forma, tem--se maior previsibilidade, pois não existem efeitos colaterais ou instanciação de dependências escon-didas e, com isso, torna-se mais fácil criar o objeto que será alvo para os testes. Para facilitar a escrita e execução dos testes, antes de tudo, deve ser fácil criar o objeto a ser testado!
Dica #10: Evite o uso de “variáveis
glo-bais”
Todo programador aprende que variáveis globais geralmente não são uma boa ideia. Entretanto, em linguagens orientadas a objetos, como Java, essas va-riáveis globais costumam aparecer “disfarçadas”, uti-lizando o artifício de métodos e variáveis estáticas da linguagem, por exemplo. Sendo mais específico, evite utilizar o padrão de projeto Singleton (este está até sendo considerado um anti-pattern atualmente), bem como criar métodos ou variáveis estáticas sem uma real necessidade. E, como já vimos (mas, não custa re-petir), não realize chamadas a métodos estáticos para obter dependências ou para realizar lógica de negó-cio nas classes de domínio do sistema. Tais artifínegó-cios sim podem ser usados, porém no contexto adequado, normalmente em classes responsáveis pela inicializa-ção do sistema, as quais realizam a instanciainicializa-ção dos objetos (eventualmente através de fábricas). Quanto mais “código estático” for utilizado, menos orientado a objetos o sistema será, e mais difícil será construir testes de unidade para ele.
Dica #11: Obedeça à Lei de Demeter
A Lei de Demeter, também conhecida como Prin-cípio do Menor Conhecimento, é uma boa orientação para desenvolvimento de software orientado a ob-jetos. Este princípio visa minimizar o acoplamento entre classes, que pode ocorrer sutilmente via cha-madas encadeadas de métodos. Em termos simples, um objeto deve evitar invocar métodos em objetos retornados por outros métodos (isto é, evitar chama-das como objeto.metodoA().metodoB()). Código menos acoplado é código mais fácil de ser testado unitaria-mente.
Vale um comentário aqui a respeito do padrão
Fluent Interface, bastante em voga no momento para
implementar linguagens específicas de domínio
(DSLs). Nesse padrão, é comum o uso de encadea-mento de métodos, visando melhorar a legibilidade do código. Nesse caso, em particular, trata-se de um idioma de programação específico, cujas interfaces já são projetadas para serem utilizadas dessa forma (ver dica #5 para um exemplo).
Além dos testes de unidade
As próximas dicas endereçam outros tipos de tes-tes automatizados, os chamados tes-testes-tes de integração, que normalmente envolvem recursos externos, como bancos de dados, rede ou interfaces gráficas.
Dica #12: Utilize DbUnit e HSQLDb para
testar a camada de persistência
DAOs (Data Access Object) são classes situadas na camada de persistência responsáveis por prover aces-so aos dados armazenados externamente a uma apli-cação. Normalmente são bancos de dados relacionais, com acesso implementado via framework objeto-re-lacional, como a JPA/Hibernate. Para escrever testes para os DAOs ou testes de integração para diversos componentes do sistema (passando por acesso a ban-cos de dados), é interessante o uso do DbUnit.
O DbUnit é um framework open-source volta-do para a configuração volta-do estavolta-do volta-do banco de davolta-dos entre a execução de vários testes automatizados. Ele é muito simples de usar, bastando especificar os da-dos desejada-dos em formato XML, os quais serão então automaticamente carregados por ele nas tabelas do banco de dados.
Testes que envolvem banco de dados também costumam ser bem mais lentos que testes de unidade. Para reduzir esse overhead, é recomendável utilizar um banco de dados em memória, como o HSQLDb. Cabe ressaltar, contudo, que ainda assim é recomen-dável que haja outro nível de testes (possivelmente os testes de aceitação ou end-to-end), que utilize o sistema de banco de dados mais próximo do possível do que será utilizado em produção.
Dica #13: Use testes de aceitação e
BDD para aumentar a qualidade externa
do sistema
A “qualidade externa” de um sistema está rela-cionada às funcionalidades do software em si, per-ceptíveis pelo usuário da aplicação (o sistema atende satisfatoriamente à necessidade de negócio de seus usuários?). Tradicionalmente se gasta um tempo com o usuário num levantamento de requisitos e regras de negócio abstratas e escrevem-se casos de uso do-cumentando o comportamento esperado do sistema
a ser construído. O problema dessa abordagem é que, no momento da implementação, acabam “surgindo” cenários de execução que não foram explicitados na especificação da funcionalidade, o que força novas interações com o cliente. As interações em si são sau-dáveis. O problema de fato é que essa descoberta tar-dia de cenários alternativos pode fazer com que toda a solução desenhada tenha que ser repensada, po-dendo acarretar retrabalho e impactos significativos no cronograma do projeto. Pior ainda é quando esses cenários são descobertos apenas após a funcionalida-de ser entregue (pela equipe funcionalida-de Q.A., por exemplo).
Uma abordagem promissora para atacar esses problemas é utilizar testes de aceitação (usando uma abordagem de Acceptance TDD ou de Behavior-driven
Development). A ideia é realizar reuniões (ou
conver-sas informais) com os usuários e explorar cenários de usos para a funcionalidade a ser desenvolvida. Literalmente, a técnica consiste no levantamento de exemplos concretos de uso do sistema. Para criar esses testes (com base nos exemplos levantados com o usuário), podemos utilizar a ferramenta JBehave, que é um framework para BDD cuja ideia é expressar testes sob a forma de cenários escritos em linguagem natural (ele possui suporte built-in a diversas línguas, entre elas o português). 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 dessa abordagem é que ela faz a ponte entre os requisitos do sistema e sua implementação, constituindo, até certo ponto, uma espécie de “documentação execu-tável” que pode ser facilmente validada pelo cliente ou especialista de domínio.
Organização
Alguém disse que “programar é gerenciar com-plexidade”, e essa complexidade também está pre-sente quando criamos testes automatizados. Por isso, é fundamental que, desde o início do projeto, sejam observadas questões referentes à organização e es-truturação do código de testes. Nesta seção, apresen-tamos algumas dicas para melhorar a organização do código dos testes automatizados.
Dica #14: Coloque as classes de teste
nos mesmos pacotes que as classes de
produção, porém em diretórios distintos
(ex.: árvores src e test).
Além de separar o código de testes do código de produção fisicamente, essa organização permite que as classes de testes acessem os membros protected das classes sendo testadas, desde que ambas as pas-tas estejam no CLASSPATH.
Dica #15: Dê às classes de teste o
mesmo nome das classes de produção
acrescidas de um prefixo ou sufixo (ex.:
“Test”).
Essa prática já é bem estabelecida na comunidade e ajuda bastante na organização. Apesar de estarem localizadas em diretórios diferentes, utilizando-se o recurso “Alt + Shift + T” do Eclipse, torna-se trivial lo-calizar uma classe de produção juntamente com sua classe de teste associada (talvez seja necessário usar asteriscos nessa busca, dependendo da regra de no-menclatura utilizada). Também existem plugins para o Eclipse que permitem alternar (via tecla de atalho) entre uma classe de produção e sua classe de teste correspondente. Além disso, ferramentas de build como o Maven, por default, já assumem que existirá uma pasta para o código de teste e que classes que possuam o prefixo ou sufixo “Test” devem ser execu-tadas como tais.
Dica #16: Separe os testes de unidade
dos testes de integração e aceitação
Um erro comum que acontece em muitos projetos é misturar testes de unidade com testes de integração na organização do projeto. No início, quando o nú-mero de testes é pequeno, não se evidenciam maiores problemas na execução de todos os testes (o que deve ser feito continuamente). À medida que o sistema cresce, porém, torna-se cada vez mais dispendiosa a execução frequente de todos os testes do sistema. O que acontece, então, é que muitos desenvolvedores acabam executando apenas os testes diretamente relacionados à funcionalidade desenvolvida no mo-mento, negligenciando a execução dos demais tes-tes devido ao elevado tempo de execução. É apenas posteriormente, talvez horas, ou mesmo dias depois, que o servidor de integração contínua irá alertar de testes quebrados em locais inesperados, dificultando o diagnóstico do problema e a sua correção por causa do feedback tardio.
Para evitar contratempos desse tipo, é recomen-dável, desde o princípio, separar os testes de unida-de, que rodam rapidamente, dos testes de integração, aqueles de execução mais lenta. Uma possibilidade é criar subpastas (ex.: unit e integration) na árvore de testes, segregando os testes de acordo com seus tipos. Com isso, depois se torna simples criar configurações de execução para um conjunto específico de testes.
Dica #17: Construa uma infraestrutura
Na minha experiência, normalmente se gasta um tempo grande para criar a infraestrutura básica para escrever os primeiros testes. Uma vez estabelecida essa infraestrutura, o tempo necessário para criar no-vos casos de testes do mesmo tipo tende a ser menor. Alguns chamam essa prática de “Arquitetura de Tes-tes”, cuja ideia é justamente investir um tempo ini-cial definindo como o teste de cada componente da arquitetura deverá ser realizado. Isso facilita a cria-ção de novos testes, principalmente para os que ain-da possuem pouca experiência. Steve Freeman e Nat Pryce recomendam que sejam escritos testes para um “walking skeleton” (esqueleto ambulante) - uma imple-mentação básica grosseira da arquitetura do sistema. Esta técnica pode ser útil no início do projeto tanto para a realização das macrodecisões de arquitetura quanto para a definição de uma boa infraestrutura de testes.
Considerações Finais
Neste artigo, apresentamos um conjunto com-pacto de dicas que podem ajudar o leitor a escrever testes automatizados com mais eficácia e qualidade, principalmente para aqueles que ainda possuem pou-ca ou nenhuma experiência nessa área. Esse assunto, devido à sua relevância, já foi bastante explorado em artigos desta revista. Para os que desejarem se apro-fundar mais nesses tópicos, recomendo a consulta dos artigos listados na seção “Para saber mais” e os livros citados nas referências do artigo. Em especial, destaco o artigo “Testes Automatizados”, que escrevi para a coluna Cinto de Utilidades da MJ 47, o qual faz um apanhado de um processo de desenvolvimento completo com BDD, TDD e ferramentas de suporte.
Testes automatizados são parte fundamental para a manutenção da boa “saúde” de qualquer base de código. Afinal de contas, se não há testes, então não pode existir refatoração contínua e, por conse-quência, a qualidade interna do sistema só tende a se deteriorar ao longo do tempo. Por isso, vale a pena investir tempo em aprender técnicas, padrões e fer-ramentas que possam auxiliar na confecção de bons testes. Juntamente com orientação a objetos e pa-drões de projeto, eu diria que um conhecimento só-lido nesse tema é uma habilidade indispensável para todo desenvolvedor profissional que se preocupa em fazer um bom trabalho.
Portanto, amigo leitor, cuide com zelo da sua obra. Não seja negligente com os testes; não negocie a qualidade!
“Quem é negligente na sua obra já é irmão do des-perdiçador.” (Pv 18:9)
> “Test-driven – Practical TDD and Acceptance TDD for Java Developers” – Lasse Koskela
> “Growing Object-Oriented Software, Guided by Tests” – Steve Freeman, Nat Pryce
> “ Test-driven Development: By Example” – Kent Beck > “Pragmatic Unit Testing in Java with JUnit” – Andy Hunt e Dave Thomas > http://alistair.cockburn.us/Walking+skeleton > http://alexandregazola.wordpress.com/2009/09/23/ testability-how-much-logic-belongs-in-the-constructor/ > http://en.wikipedia.org/wiki/Law_of_Demeter > http://www.aniche.com.br/2011/01/um-pequeno-estudo-sobre-assercoes-em-testes/
/referências
> “Fixture-Factory – Criando objetos para seus testes” – MJ 59
> “Benefícios do TDD medidos na prática” – MJ 54 > “Arquitetura Ágil” – MJ 50
> “Cinto de Utilidades: Testes Automatizados” – MJ 47 > “Behavior-driven Development em Java na prática, com JBehave” – MJ 44
> “Evolução do Design através de Testes e o TDD” – MJ 41 > “Automatização de testes de persistência com FIT, DBUnit e HSQLDB” – MJ 38
> “Testes de Unidade para Camadas de Persistência no Mundo Real” – MJ 24
> “Testes Unitários para Camadas de Negócios no Mundo Real.“ – MJ 23