Engenharia
Testar é diferente de escrever testes
Testar é diferente de escrever testes. Desenvolvedores escrevem testes para ter espaço para pensar e confiança para refatorar. Testar tem como foco encontrar bugs. As duas práticas são necessárias.
Chegamos a um ponto em que os desenvolvedores sabem que precisam escrever testes automatizados. Afinal, ideias como Extreme Programming e Test-Driven Development, de Kent Beck, Michael Feathers sobre sinergia entre teste e design, Steve Freeman e Nat Pryce sobre como desenvolver um software orientado a objetos guiado por testes, DHH e Ruby on Rails ao garantir que um framework web venha com um framework de testes, entre outras, se consolidaram.
Essas ideias também ficaram comigo. Tenho tentado escrever o máximo possível de testes automatizados para os sistemas de software em que trabalho desde 2007 (posso contar a história de quão traumático foi meu projeto de 2006 em uma postagem posterior). Fiquei tão entusiasmado com o Desenvolvimento Orientado a Testes que até fiz dele o tópico da minha tese de mestrado. Em 2012, achei que tinha experiência suficiente com o tópico, então decidi escrever um livro sobre como eu estava praticando TDD, bem como um livro sobre os fundamentos de ferramentas como JUnit e Selenium (infelizmente, disponível apenas em português brasileiro).
Depois de muitos anos de programação em pares, vejo desenvolvedores usando código de teste automatizado como uma espécie de rede de suporte. Os testes ajudam a pensar com clareza sobre o que implementar e dão suporte às inúmeras refatorações que acontecem durante o desenvolvimento.
Na verdade, pesquisas mostraram algumas vezes que fazer TDD pode melhorar o design de sua classe (veja Janzen, Janzen e Saiedian, George e Williams, Langr, Dogsa e Batic, Munir, Moyaaed e Petersen, eu, entre outros). Recentemente, Fucci et al. argumentaram que a parte importante é escrever testes (e não importa se é antes ou depois). Essa também é a percepção dos profissionais, como mostra uma postagem recente no blog de Feathers: “Essa é a mágica, e é por isso que os testes unitários também funcionam. Quando você escreve testes unitários, no estilo TDD ou após o desenvolvimento, você examina, pensa e muitas vezes evita problemas sem sequer encontrar uma falha no teste.”
Escrever testes automatizados é, portanto, algo a ser recomendado.
Pragmaticamente falando, sinto que, ao usar código de teste como forma de fortalecer a confiança, testes do caminho feliz são muitas vezes suficientes. E, de fato, não há necessidade de muito conhecimento ou teorias para testar o caminho feliz: basta saber como tornar os testes possíveis (os desenvolvedores falam muito sobre projetar para testabilidade) e usar as melhores ferramentas disponíveis (e os desenvolvedores realmente dominam ferramentas como JUnit, Mockito e Selenium).
O que defendo é que, após implementar o código de produção (com todo o suporte do código de teste de caminho feliz), o próximo passo deveria ser testá-lo adequadamente. O teste de software consiste em encontrar muitos bugs; trata-se de explorar como o seu sistema se comporta não apenas no caminho feliz, mas também em casos excepcionais e extremos.
Recentemente perguntei aos meus alunos do primeiro ano de ciência da computação que tipos de erros eles cometeram em seus programas até agora, o que os ensinou que o teste é importante. Recebi uma lista enorme:
-
Programas que não funcionam quando um loop não itera (a condição do loop nunca foi avaliada como verdadeira e o programa travou).
-
Programas que não funcionam porque deixaram de executar a última iteração (o famoso erro off-by-one).
-
Programas que não funcionam quando as entradas são inválidas, como null, ou não estão de acordo com o esperado (por exemplo, uma string que representa o nome de um arquivo a ser aberto contendo um espaço extra no início).
-
Programas que não funcionam porque o valor está fora do intervalo esperado ou exatamente no seu limite. Por exemplo, se você tiver um if que espera que um número seja maior que 10, o que acontece se o número for menor que 10? Ou precisamente 10?
-
Dependências que não existem, por exemplo, seu programa lê um arquivo, mas o arquivo pode não estar lá.
-
Entre outros.
Independentemente da sua experiência, tenho certeza de que você também já enfrentou alguns desses bugs antes. Mas com que frequência os desenvolvedores realmente exploram e escrevem esses casos de teste?
Tenho a impressão de que isso acontece pouco. (Se você é um pesquisador empírico de engenharia de software, esta essa é uma boa pergunta para investigar).
É claro que, quando se trata de teste de software, pesquisadores e profissionais falam sobre diferentes tópicos. Livros pragmáticos muito bons, como Pragmatic Unit Testing in Java 8 with JUnit, têm um forte foco em como fazer automação de testes. Embora forneçam boas dicas sobre o que testar (os capítulos sobre CORRECT e RIGHT-BICEP no livro da PragProg são interessantes), eles geralmente não vão além disso.
Por outro lado, livros de teste de software da academia, meu favorito é Análise de Teste de Software de Young e Pezzè, têm um forte foco em teorias e técnicas sobre como projetar casos de teste que exploram o máximo possível do programa em teste. No entanto, com poucos exemplos oportunos sobre como aplicar essas ideias, o que é definitivamente um requisito para os profissionais.
Robert Binder em seu livro de 2000 (p.45) faz uma distinção entre testes “direcionados a falhas” e “direcionados à conformidade”:
-
Testes direcionados a defeitos: a intenção é revelar defeitos observando falhas.
-
Conformidade direcionada: intenção de demonstrar conformidade com os recursos exigidos (por exemplo, atende a uma história de usuário, concentra-se no comportamento do caminho feliz).
Eu argumento que um desenvolvedor forte deve testar de ambas as perspectivas. Eles são complementares.
Como desenvolvedor, se você quiser levar seus testes para o próximo nível, você definitivamente deve se familiarizar com o que os pesquisadores têm feito, pois eles têm conduzido um belo trabalho na área de testes de software, que acredito ser definitivamente aplicável na prática. Alguns exemplos (mas definitivamente não uma lista completa):
-
Detectar se algum conjunto de testes é capaz de detectar possíveis bugs. A ideia de teste de mutação está sendo aplicada no Google.
-
Gerar testes automaticamente (o famoso exemplo é o EvoSuite, que é capaz de gerar testes para classes Java) ou para reproduzir travamentos automaticamente. Eu mesmo trabalhei em gerar testes para consultas SQL automaticamente.
-
Derivação de modelos dos logs do seu sistema de produção e construção de modelos que podem ser analisados por especialistas do domínio.
-
Fornecer diferentes critérios de cobertura de código (você provavelmente tem alguma ferramenta de cobertura de código em seu CI) e entender os benefícios de cada um deles.
-
Várias ferramentas para testar aplicativos da web, como explorar automaticamente o aplicativo da web em busca de falhas, bem como encontrar falhas em APIs REST. Mais informações podem ser encontradas nesta revisão da literatura de testes da web.
-
Medir empiricamente quanto os desenvolvedores testam, se os test smells são importantes ou se eles são a causa de testes instáveis, por que e como os mocks são usados em sistemas Java, e como os desenvolvedores revisam o código de teste durante as revisões de código.
-
Os testes baseados em propriedades também estão chegando aos profissionais por meio de ferramentas como ScalaCheck e QuickTheories.
-
Entre outras coisas. E se você quiser entrar nessa luta, leia este resumo de Antonia Bertolino sobre os atuais desafios da pesquisa em testes.
Escrever testes é diferente de testar. Ambos fornecem aos desenvolvedores resultados diferentes. O primeiro dá confiança e apoio durante todo o desenvolvimento. Este último garante que o software foi avaliado também nos cenários que podem revelar defeitos. Embora ambos sejam importantes, é hora de ambas as comunidades se alinharem:
-
Os desenvolvedores devem se familiarizar mais com técnicas de teste de software mais avançadas (que os pesquisadores têm fornecido). Eles são definitivamente um bom complemento à rede de segurança dos testes e os ajudarão a testar melhor seus softwares.
-
Educadores devem ensinar ambas as perspectivas. Minha percepção é que as universidades ensinam técnicas de teste de forma abstrata (os desenvolvedores precisam aprender como usar as ferramentas!), enquanto os profissionais ensinam apenas como usar as ferramentas automatizadas (os desenvolvedores precisam aprender como testar corretamente!).
-
Pesquisadores devem entender melhor como os profissionais têm evoluído no campo de testes de software a partir de sua perspectiva, bem como para compartilhar melhor seus resultados, já que os desenvolvedores não leem artigos. O jargão de pesquisa é para pesquisadores.
Atualização: Mariefred (da R/Programming) sugeriu este link relacionado: Teste e verificação refinados.
Agradecimentos: Agradeço a Alberto Souza (Caelum, São Paulo), Anderson Leite (RG/A, São Francisco) e Arie van Deursen (Delft University of Technology) pelos comentários nas versões anteriores deste post.