Aqueles familiarizados com as metodologias ágeis de desenvolvimento e o eXtreme Programming de Kent Beck certamente já ouviram falar de desenvolvimento guiado por testes (do inglês, Test Driven Development – TDD). Trata-se de um processo no qual o desenvolvedor é incentivado a escrever código de teste antes mesmo do código da aplicação em si. Apesar de ser uma metodologia relativamente antiga, descrita antes do famoso livro de Beck, o TDD ganhou maior notoriedade a partir da publicação do referido autor e é de grande valia para startups e empresas de tecnologia, visto que inspira confiança e encoraja designs simples.
O ciclo de desenvolvimento guiado por testes pode ser simplificado em três passos:
- Escreva um teste e verifique que ele falha;
- Escreva uma quantidade mínima de código que faça com que o teste passe;
- Refatore (modifique) o código até atingir a qualidade desejada.
Benefícios
Esta abordagem, apesar de simples, traz diversos benefícios relacionados ao desenvolvimento do sistema em si e sua manutenção, por exemplo:
Aproximação entre o Product Owner (PO) e o time de desenvolvimento
Quando o PO é capaz de expressar as funcionalidades de um produto na forma de casos de testes, estes podem ser facilmente implementados e validados durante o desenvolvimento do produto. É uma forma de facilitar a aderência do produto à visão desejada. É evidente que nem sempre isso é fácil de se atingir, especialmente quando o PO não possui background técnico, mas a criação de casos de teste básicos ou até mesmo critérios de aceitação objetivos como forma de especificação pode guiar de maneira muito mais concreta a equipe responsável pelo desenvolvimento, evitando retrabalho.
Qualidade do código
Um time acostumado a desenvolver guiado por testes geralmente cria código de maior qualidade. Isso se dá pois os desenvolvedores veem a necessidade de escrever código coeso e testável, no qual as responsabilidades por cada funcionalidade são bem divididas entre os objetos e métodos implementados. Esta abordagem também incentiva os membros do time a pensar previamente como serão implementadas todas as interfaces e APIs do sistema em desenvolvimento.
Manutenção
Quando um sistema é concebido com testes, a sua manutenção é imensamente facilitada. Os desenvolvedores podem rodar todos os testes sem muito esforço após desenvolver novas funcionalidades e verificar quais passaram a falhar, isolando o problema imediatamente e gerando uma melhor compreensão do código como um todo. Além disso, desenvolver com testes gera uma maior segurança para realizar mudanças no código, reduzindo a incerteza dos integrantes do time, o débito técnico e, mais uma vez, a quantidade de retrabalho.
Documentação
O código de teste não substitui a documentação do código, mas a complementa. O teste é também a visão do desenvolvedor com relação a como uma função deve ser invocada ou uma classe deve ser utilizada. Desta forma, quando outro desenvolvedor precisa entender a intenção original, a leitura dos casos de teste facilita o processo.
Comumente o desenvolvimento guiado por testes é associado com tempos mais longos de desenvolvimento ou com a criação de código que não serve para efetivamente desempenhar a função desejada. Apesar de isso ser verdade em projetos pequenos, à medida que um projeto cresce e evolui, os benefícios elencados se tornam mais evidentes e aceleram o desenvolvimento, visto que os desenvolvedores ganham maior confiança para criar funcionalidades e o PO cria maior proximidade com os membros do time. Um investimento inicial em testes se paga ao longo da vida útil do produto, mas em alguns casos, o esforço para escrever testes para um sistema já em produção pode ser proibitivo em termos de tempo e custo.
Relação com DevOps e CI/CD
Com o crescimento dos produtos 100% digitais, fortaleceu-se o desenvolvimento ágil de software e as empresas se depararam com a necessidade de realizar deploys frequentemente para responder aos feedbacks do mercado. Esta necessidade fomentou a adoção de um conjunto de práticas de DevOps, as quais buscam reduzir o tempo entre o desenvolvimento de uma nova funcionalidade e a sua disponibilização para o cliente final em ambiente de produção.
O desenvolvimento guiado por testes tem forte relação com essa abordagem, pois permite configurar a execução automática dos testes sempre que uma nova funcionalidade é criada em um fluxo de integração contínua (CI). Desta forma, antes do deploy em si, é fácil de verificar que todos os testes passaram, criando uma camada adicional de segurança.
Ao mesmo tempo, com o uso de versionamento de código, quando ocorrem falhas em produção, é simples reverter a aplicação para uma versão estável. Nestes casos, busca-se identificar o problema ocorrido e escrever testes que capturem a falha identificada, de modo que em deploys futuros esta falha seja previamente eliminada.
Boas práticas
O início do desenvolvimento guiado por testes pode ser bastante turbulento, pois requer uma mudança significativa na forma com que os desenvolvedores trabalham. Entretanto, é possível elencar uma série de itens para facilitar o processo de aprendizagem.
Testes simples
Os desenvolvedores devem focar em desenvolver testes simples, que se preocupam em validar uma única coisa. Testes extensos ou muito complexos não necessariamente vão auxiliar a inspeção do código ou ajudar a identificar o ponto de falha. É preferível dividir um teste complexo em vários testes simples.
Testes independentes
O contexto de execução de um teste deve ser “limpo” e não depender de um estado anterior do método ou da classe sendo testado. Similarmente, um teste deve rodar de maneira idêntica independente da ordem de execução com relação aos demais testes.
Testes bem estruturados
Um teste deve seguir uma estrutura padrão, comumente dividido em setup, execução, validação (ou verificação) e cleanup (ou teardown). Além de facilitar o entendimento do teste, esta estrutura ajuda a manter a independência entre eles.
Testes rápidos
Os testes devem sempre ser rápidos, de modo que não seja custoso rodar os testes sempre que for interessante. Se os testes são lentos, os desenvolvedores vão se apoiar cada vez menos nos testes para balizar o desenvolvimento.
Relatórios de cobertura de testes
Os relatórios de testes fornecem aos membros do time informações quanto à cobertura, um indicador de qual o percentual do código do sistema é executado durante os testes. Com estes relatórios é possível identificar quais módulos estão pouco testados e criar testes direcionados. Idealmente estes relatórios podem ser gerados diretamente no pipeline do projeto e disponibilizados facilmente aos desenvolvedores.
Aplicações
Embora seja uma técnica com origem na área de desenvolvimento de software, hoje o TDD encontra aplicações em outros campos. A forma mais básica e simples de desenvolver guiado por testes é implementando testes unitários. Estes são responsáveis por verificar os comportamentos mais básicos do código, comumente uma função ou método de uma classe. Apesar de serem os mais simples, os testes unitários são úteis para testar as partes do programa de forma individualizada, gerando indicadores específicos de falhas.
Um passo posterior na aplicação do TDD é a implementação de testes de integração, em que são testados em conjunto dois ou mais módulos de uma aplicação. Estes testes normalmente não validam somente funções em contextos isolados, mas sim o resultado da combinação de diversos métodos e objetos para se verificar um requisito funcional do produto em desenvolvimento.
Em produtos digitais disponibilizados por meio da internet, uma das preocupações é referente ao desempenho do sistema quando ocorrem múltiplos acessos simultâneos. Nestes casos, é importante realizar testes de carga da aplicação, um método de testagem no qual o comportamento esperado dos usuários é simulado e o sistema é submetido a acessos de diversos usuários simulados, gerando um indicativo da qualidade do serviço prestado.
Quando o software desenvolvido é dependente de uma plataforma de hardware específica, como é o caso de sistemas embarcados, é interessante que seja feito um investimento em testes de integração de hardware/software. Estes testes geralmente são acompanhados de uma jiga de testes (test jig) que permite estimular as entradas e validar as saídas do equipamento sob teste. A depender do sistema, tais testes podem ser realizados com finalidades distintas, desde a validação de uma funcionalidade até a detecção de defeitos de fabricação que podem levar o produto a uma falha prematura. Este tipo de teste também permite submeter o sistema a situações difíceis, perigosas ou custosas de se replicar na prática.
Aqui na MMZtech nos preocupamos em criar o seu produto em um ciclo de desenvolvimento que fomenta a aproximação de todas as partes, viabilizando o seu crescimento e evolução por meio de testagem contínua.
Tem uma ideia de produto ou problema a resolver? Fale conosco, nós podemos ajudá-lo a fazer acontecer.