TDD no Frontend: é possível?
Um dos temas que mais ganhou popularidade ultimamente na comunidade de desenvolvimento de software foi o TDD: a sigla para Test Driven Development, ou Desenvolvimento Orientado por Testes.
A grande premissa do TDD se resume em codificar o teste antes mesmo do código existir. Se bem aplicado, pode-se garantir testes bem documentados, uma vez que guiamos o desenvolvimento do sistema a partir de exemplos de comportamento.
Apesar desta metodologia estar em alta, ela é bastante antiga. Kent Beck já tinha idealizado o TDD como parte de sua metodologia de Extreme Programming nos anos 90.
Entendendo o ciclo de TDD
Temos três etapas:
- Red: escreva um pequeno teste automatizado que, ao ser executado, irá falhar;
- Green: implemente um código que seja suficiente para ser aprovado no teste recém-escrito;
- Refactor: refatore o código, afim de ser melhorado, deixando-o mais funcional e mais limpo.
Para realizar o teste em cada funcionalidade do seu código, você sempre irá seguir estas etapas. Elas são comumente referenciadas como red-green-refactor ou red-green-blue.
Testes no frontend
Antes de abordarmos o tema do TDD no frontend, precisamos primeiro entender a prática de escrita de testes desse ecossistema ao passar dos anos.
Atualmente temos diversas bibliotecas e frameworks para trabalhar com o conceito de Single Page Applications. React, Angular e Vue.js são ferramentas muito consolidadas no mercado, e é quase de cunho obrigatório o aprendizado de pelo menos uma delas para trabalhar como um desenvolvedor frontend em qualquer empresa.
Mas antes da chegada dessas tecnologias, o desenvolvimento frontend era bem diferente. A complexidade das aplicações era bem menor, consequentemente não tínhamos tantos padrões arquiteturais e não se pensava tanto em escalabilidade. Portanto era incomum a escrita de testes automatizados. Não se via tanto valor em testar Interfaces. Apesar da existência de ferramentas para testes End-to-End, elas não eram ideais para a realidade da maioria dos sistemas.
O frontend moderno
A web como conhecemos hoje evoluiu muito em pouquíssimo tempo. Os browsers possuem funções muito maiores do que a simples navegação na internet. Temos aplicações extremamente robustas, e com isso, temas como qualidade de código e performance começaram a ficar mais comuns neste universo.
As tecnologias citadas acima acompanham essa evolução da web, e todas elas trabalham com a arquitetura de componentes. Basicamente um componente representa um pedaço da User Interface final.
Tendo em mente que um componente é uma unidade do nosso sistema, foi ficando mais concreta a ideia do que é um teste unitário no frontend. Tendo essa separação, fica muito óbvio que devemos escrever testes unitários para nossos componentes, afim de garantir que eles estejam funcionando de forma isolada. Também é válido testar a integração de vários componentes com um propósito em comum.
No React, é muito comum utilizarmos um framework de testes chamado Jest para testar nossos componentes. Já no Angular, ao iniciar um projeto, temos um setup completo de testes com o Jasmine e o Karma.
Com a realidade que temos hoje, trabalhar com testes automatizados no frontend é um tema muito mais aquecido do que era antigamente. Mas infelizmente ele está longe de ser praticado no dia-a-dia da maioria das empresas. Acredito que ainda temos um caminho a percorrer pra que essa prática torne-se cada vez mais comum.
Portanto, o desenvolvedor frontend que sabe trabalhar com testes automatizados e aplica no dia-a-dia, tem um grande diferencial e se destaca da maioria.
Se os testes automatizados não são tão frequentes, a técnica do TDD no frontend é menos ainda. Mas talvez você pode estar pensando: “Se eu começar a aplicar o TDD no meu projeto, vou me destacar ainda mais!”. Muita calma! Talvez não seja bem assim.
TDD não é tão essencial quanto você imagina
E vamos de polêmica. No início deste artigo mencionei que existe um certo “hype” atualmente com o TDD. Este tema é cada vez mais mencionado na comunidade. Não que isso seja algo ruim, muito pelo contrário. É ótimo que mais pessoas tenham ciência da importância em escrever testes e o quanto isso agrega na qualidade e manutenção de um software.
O grande problema é que muitos desenvolvedores, principalmente os iniciantes, ouvem esse termo e já querem aprender pelo motivo errado. Não é difícil de encontrar frases do tipo: “Se você não aplica TDD no dia-a-dia você não é um desenvolvedor de verdade”.
TDD não é fácil
O processo de aprendizagem do TDD é árduo. Envolve muita prática pra que se torne um hábito. A maioria das pessoas não aprende a programar começando pelos testes, o que dificulta muito para que o desenvolvedor consiga “virar a chave” no cérebro.
Se você escreve os testes depois do código de produção, você está entregando qualidade da mesma forma. Fazer o processo ao contrário não vai garantir mais qualidade.
O TDD pode garantir testes menos enviesados e diminuir o retrabalho. Ele ajuda também a entender com mais clareza os requisitos do sistema, mas infelizmente nem sempre temos 100% do entendimento do que é necessário ser construído.
Então não é possível aplicar TDD no frontend?
No meu ponto de vista, para que seja possível aplicar o TDD, as tarefas precisam estar acompanhadas de:
- Requisitos bem claros, se possível acompanhados de user stories
- Protótipos construídos por um UI/UX Designer
Se essa não é a sua realidade, o TDD pode mais atrapalhar do que ajudar. Se você não tem em mãos o que precisa ser feito, será muito difícil pensar nos testes primeiro.
Como saber o que testar?
O frontend moderno geralmente possui mais complexidade do que antigamente, e isso gera mais regras no lado do cliente. Muitas das vezes essas regras são funções que não envolvem UI. Podemos testá-las da forma tradicional, principalmente se forem funções puras.
Já para os testes de UI, que geralmente são representados por componentes, existe uma certa controvérsia. Algumas pessoas têm a opinião de que não compensa testar a interação com o DOM de forma unitária. Elas preferem deixar essa responsabilidade para os testes end-to-end. Eu discordo dessa opinião, pois os testes end-to-end requerem um custo de criação e execução muito maior do que os testes de integração e de unidade.
Tomando como base a Pirâmide de Testes, uma boa opção seria criar testes end-to-end para os fluxos principais da sua aplicação e também para pontos críticos. Podemos então testar a integração de vários componentes ou um componente isolado com mais frequência, cobrindo os mais diversos cenários utilizando Mocks para comunicações com APIs.
Se a sua equipe utiliza o BDD (Behaviour Driven Development) para escrever user stories, elas podem ser boas candidatas para os testes end-to-end. Dessa forma fica mais fácil aplicar o TDD nesta camada. Ferramentas como o Cypress e o Cucumber podem facilitar muito esse fluxo.
O BDD é uma técnica de desenvolvimento ágil que incentiva a colaboração entre os membros da equipe e tem como foco a descrição do comportamento do programa que será construído.
Para aplicar o TDD nas camadas abaixo (Integração e Unidade) reforça-se mais uma vez a importância de possuir um protótipo. Nela sabemos exatamente como nossa UI deve se parecer e como se comportar. Para esses tipos de teste, a biblioteca Testing Library pode ser uma boa opção. Ela possui uma filosofia interessante de evitar detalhes de implementação, fornecendo várias abstrações para a interação com o DOM. Recomendo a leitura deste artigo onde explico mais sobre ela em conjunto com o Angular.
Estratégia de testes com TDD
Joshua Morony demonstrou em um vídeo a estratégia utilizada por ele para aplicar o TDD durante o desenvolvimento frontend. Ela pode ser útil em vários casos, mas perceba: se os requisitos não são claros como demonstrei anteriormente, esta estratégia é simplesmente impossível. A abordagem consiste em 8 passos:
- Escrever o teste end-to-end;
- Ver ele falhar;
- Escrever os testes unitários e/ou de integração;
- Ver eles falharem;
- Implementar a funcionalidade para os testes unitários e/ou de integração;
- Ver os testes unitários e/ou de integração passarem;
- Verificar se agora o teste end-to-end está passando;
- Se não estiver passando, adicionar mais testes unitários/integração (Voltar para o passo 3).
Esse ciclo se repete para cada user story implementada. Os passos seguem de cima para baixo, ou seja, iniciamos de forma mais abstrata com o teste end-to-end e vamos amplificando os detalhes ao descer o nível da implementação.
Podemos exemplificar com um cenário de listagem de usuários.
Tendo a história de usuário, podemos transcrevê-la para o nosso teste end-to-end. Caso você utilize o Cypress, é possível integrá-lo com o Cucumber.
Agora basta escrever os testes baseados nestes steps que são escritos na linguagem Gherkin. Para isso, podemos utilizar o padrão de PageObjects.
Vale lembrar que esta implementação deve ser mais abstrata. Evite ser muito específico na escolha dos elementos, caso contrário o nível de retrabalho será maior.
O próximo passo é a criação do componente, mas primeiro começamos com o teste. Essa é a etapa mais complicada, pois nem sempre temos ideia de quais elementos do DOM vamos precisar interagir. Para isso podemos utilizar uma propriedade chamada data-testid
, que servirá somente como um helper para os testes. E isso vale também para o teste end-to-end.
<table data-testid="table">
</table>
Para facilitar a prática do TDD e evitar detalhes de implementação, podemos utilizar a Testing Library. A grande vantagem é que ela possui suporte para todas as principais tecnologias do mercado. Com ela é possível ter uma ideia da escrita do teste antes da implementação de fato, além de deixar o teste mais semântico.
Após a escrita de todos os testes, partimos para a implementação de fato. O cenário perfeito, após finalizar a codificação, seria assistir os testes end-to-end e os testes integrados/unitários passarem.
Neste momento, no ciclo do TDD, refatoramos o nosso código. Dependendo do contexto, separamos os componentes em responsabilidades únicas.
Mas é claro que podemos ter muitos percalços no caminho. Somente com a prática que podemos aperfeiçoar este fluxo, e mesmo assim não temos a garantia que o processo será sempre fluído.
Vale mencionar que essa é somente uma estratégia para aplicar o TDD. Ela pode funcionar para alguns cenários mas talvez para o seu cenário possa não fazer tanto sentido. Não existe uma estratégia perfeita.
Procure sempre ter senso crítico e não assumir a opinião alheia como verdade absoluta. Observe pontos de vista diferentes e tire suas próprias conclusões do que pode ser o melhor para o seu projeto.
Conclusão
Neste artigo podemos ter uma visão do que é o TDD e se é possível aplicá-lo no frontend. Dado o contexto de testes no frontend, foram apresentadas estratégias e técnicas para este feito.
O ponto mais importante é entender que o TDD não é obrigatório para o desenvolvimento. Ele é uma metodologia que pode te ajudar durante todo o fluxo de trabalho, mas para que isso seja possível deve-se ter em mãos os requisitos do sistema muito claros.