Testes Unitários no Frontend

Testes Unitários no Frontend

Muito bem, agora você é um Desenvolvedor Frontend, já fez alguns projetos aqui e ali, implantou algumas aplicações que o ajudaram a compreender conceitos essenciais de desenvolvimento, mas há uma coisa que você não colocou em prática, seja porque até agora você estava evitando, ou simplesmente porque você não entende e não tem noção dos benefícios que isso pode trazer, estamos falando dos tão temidos testes. Mais especificamente, os testes unitários no frontend, uma parte **muito importante **do ecossistema de testes.

Testes no frontend podem ser divididos em 3 categorias:

Existe esse conceito chamado de Pirâmide de testes, onde a ideia é que você comece pela base da pirâmide e em seguida vá escalando os seus testes. ****A quantidade de testes diminui conforme vamos subindo na pirâmide, pois aumenta o esforço necessário e o tempo de execução de cada tipo de teste.

É interessante notar também que vemos mais testes unitários na base, por serem mais rápidos de desenvolver e rodar. E é sobre eles que vamos falar com mais detalhes hoje.

Mas antes disso, vamos entender o que é cada tipo de teste:

  • E2E Testing ou Testes end-to-end

É a prática de testar se a execução de uma aplicação está funcionando como planejada do início ao fim. Toda a aplicação é testada, como a comunicação entre componentes, banco de dados, rede, APIs, etc. E também executa seu código em vários tipos de navegadores, pois pode acontecer de existir algumas diferenças de renderização entre um e outro.

  • Integration Testing ou Testes de integração

Teste de integração acontece quando os componentes são testados em grupo para garantir que não haja nenhuma quebra naquilo que foi feito unitariamente e naquilo que está sendo integrado junto. O ideal é que sejam feitos os testes unitários primeiro e, depois, seja feito o teste de integração que vai verificar se os componentes funcionarão juntos.

  • Unit Testing ou Testes unitários

Testes unitários, são códigos que testam partes pequenas do seu código que podemos chamar de unidade, dado um comportamento ou entrada dessa unidade, você testa a regra de negócio ou saída dessa unidade. Essas unidades geralmente assumem a forma de métodos, propriedades, ações de elementos UI, etc.

A estrutura de um frontend de uma aplicação web poderia ser comparada a um quebra-cabeça. Se uma das peças se quebrar de forma que não possa mais ser encaixada a outra peça, a imagem ficará comprometida e a peça quebrada precisará ser consertada.

Entendendo os Testes Unitários

Os testes unitários são os testes mais básicos da cadeia de testes no desenvolvimento de frontend. Ao contrário do que os outros tipos de testes nos traz, os testes unitários garantem que uma pequena parte de código, também chamado de unidade, esteja funcionando mesmo que desconectada de toda a aplicação. Podemos considerar uma unidade como sendo a menor parte de um código. Pode ser uma função, ou um botão, por exemplo.

Quando desenvolvemos aplicações frontend, muitas vezes trabalhamos com componentes. Os testes unitários então, nos garante que eles funcionem como o esperado de forma isolada, mas não se esqueça, também é preciso saber se esses componentes funcionam quando utilizados de forma conjunta.

O principal objetivo dos testes é nos ajudar a garantir uma entrega com qualidade, e por mais que dedicaremos algum tempo na execução dos testes, no final do dia estaremos ganhando tempo para toda equipe.

Pense comigo, subimos uma aplicação para produção, uma página de login por exemplo, e por alguma alteração que foi realizada no componente de input esse login para de funcionar. Com isso o usuário já abriu um chamado para o suporte, o time de suporte ficou cerca de meia hora para entender qual era o problema, depois o dev responsável gastou mais uma hora para resolver o problema e subir para produção, e depois a pessoa responsável pelo atendimento deu uma devolutiva para o usuário.

Percebe que paramos 3 funcionários de suas funções para resolver UM problema que poderia ter sido detectado caso houvesse algum teste?

É essa a mentalidade que deve ser implantada nos times. Pensar nos benefícios ao invés do tempo "perdido" na execução do teste.

Escreva código fácil de testar

Partimos do famoso conceito “Keep it simple, stupid".  Fazemos isso tendo em mente alguns princípios enquanto estamos desenvolvendo, como por exemplo, manter uma única responsabilidade para cada pedaço de código (um botão deve ser apenas um botão, e não um link), procurar ter menos dependências entre diferentes partes do código e, ainda mais importante para o frontend, manter a lógica de negócio separada da interface da aplicação.

Quando escrevemos código fácil de testar, consequentemente temos um código mais legível, simples de dar manutenção e de adicionar novas funcionalidades.

Uma outra maneira de se ter um código claro e de fácil leitura, é ter o seu desenvolvimento guiado por teste, ou seja, escrever os testes antes mesmo de implementar o código. É o que conhecemos por TDD (Test Driven Development). Desta maneira, pensamos primeiro no comportamento que estamos esperando para determinada funcionalidade da unidade, e então escrevemos um teste com base nisso. Esse fluxo também pode ser usado para fazer refatorações, pois garante que aquele pedaço de código faz somente o que é esperado dele e que está funcionando corretamente.

Uma outra dica é sempre pensar que tudo em testes é uma simulação. Seria como se fosse uma réplica das funcionalidades, só que todos os dados e ações são "mockados" ou simplesmente falsos.

Testes unitários bem escritos nos ajudam a ser menos enganados por falsos positivos de funcionamento.

Desenvolver uma aplicação já com uma base de testes bem pensada é uma maravilha na teoria, mas infelizmente sabemos que na prática é muito mais complicado. Muitas vezes a maioria da aplicação já foi escrita e praticamente sem nenhum teste! Então, como saber por onde começar a testar aquilo que já existe?

Nessa caso, faria mais sentido começar pelo fim. Mas como? Utilizando testes E2E (end-to-end) para garantir que a aplicação funciona da maneira esperada pelo usuário, testando os principais fluxos da aplicação e garantindo que eles irão permanecer funcionando quando fizermos alterações ou incluirmos alguma feature nova.

Feito isso, podemos começar a adicionar os outros tipos de testes durante o desenvolvimento e ir equilibrando aos poucos.

Uma ótima maneira de saber o que testar é ir do zero, olhando para os componentes ou fluxos de uma aplicação. "O que meu método toma como entrada e qual é o seu resultado?", "Meu método afeta o estado do meu componente?", "Quais são os casos de uso?". Essas são boas perguntas para encontrar um ponto de partida.

E lembre-se, não importa qual seja a estratégia que a sua empresa escolha seguir, o que importa é começar uma conversa sobre o tópico e estimular a cultura de testes, mas tendo sempre em mente que todo o time é responsável pela qualidade da aplicação.

A estrutura de um teste unitário

Antes de construirmos nosso primeiro teste unitário, vamos entender qual é a estrutura básica de um teste. Caso você queira acompanhar e testar na sua máquina, recomendo o site TDDBin onde você pode simular testes de graça e sem a necessidade de instalar nada na sua máquina.

Vamos entender o que foi escrito:

  1. O método que queremos testar. Como observamos anteriormente, os testes unitários muitas vezes se aplicam a métodos, componentes ou interações de elementos de UI.
  2. Um conjunto de testes, que deve ser descrito brevemente e agrupa testes unitários que estão relacionados. Por exemplo, um conjunto de testes pode incluir todos os testes que dizem respeito a um método específico. Você pode declarar quantos conjuntos de testes quiser, seu objetivo é tornar seus registros de testes mais legíveis.
  3. Um teste unitário, acompanhado de uma descrição. A declaração (4) dentro da chamada de retorno é o teste em si.
  4. Uma afirmação de teste. Testes são todos sobre afirmações, comparamos um determinado valor com um valor esperado. Aqui, damos o valor de retorno do nosso método  add com 1 e 1 como parâmetros e esperamos que o resultado seja 2.

Simples, certo? É essa a estrutura básica que vamos usar para construir os nossos testes a seguir.

Pré-requisitos

Para seguir adiante com este tutorial, você precisa do Node.js  (v6 e acima) e npm instalado em sua máquina. Além de conhecimento básico em React.js.

Exemplo real de um teste unitário

O exemplo mostrado anteriormente foi feito somente para conhecermos qual a estrutura básica de um teste, agora vamos aplicar testes em um componente real do começo ao fim.

Podemos encontrar várias ferramentas de testes no mercado, hoje vamos utilizar as mais comuns: Jest e Enzyme.

Conhecendo as ferramentas

Jest é uma ferramenta de teste do Facebook que facilita a realização de testes unitários em JavaScript. Já o Enzyme, é uma ferramenta específica para React. Ela fornece vários métodos úteis que melhoram a forma como testamos componentes React.

Vamos ver como Jest e Enzyme podem ser utilizados para criar aplicações React mais robustas.

Pra criar o template da nossa aplicação onde iremos construir os testes, vamos utilizar o create-react-app

Em seu terminal escreva:

npx create-react-app unit-tests

E então no seu navegador preferido abra a aplicação.

Normalmente, precisaríamos instalar e configurar o Jest antes de escrever qualquer teste, mas utilizando o create-react-app o Jest já vem instalado, então não precisamos fazer nada além disso. Podemos então, pular diretamente para escrever nosso primeiro teste.

Observando as pastas da aplicação você vai encontrar o arquivo App.test.js **que já vem com um teste criado:

No próprio terminal do editor de código rode  npm run test

Você deve ver este resultado:

Parabéns! Você rodou o seu primeiro teste unitário.

Criando seu primeiro teste

Vamos construir um contador básico que permite que o usuário clique em um botão e aumente o valor que aparece na tela.

No arquivo App.js vamos alterar o código existente pelo seguinte:

E no terminal rode:

npm start

Sua aplicação estará rodando em localhost:3000 e o resultado será:

Antes de começar a escrever os nossos próprios testes precisamos instalar o Enzyme, a outra ferramenta que falamos anteriormente e que vai nos ajudar na construção dos testes:

npm install enzyme enzyme-adapter-react-16 --dev

Após a instalação temos que criar o arquivo setupTests.js dentro da pasta src.

Este arquivo comunica ao Jest e ao Enzyme quais Adapters nós iremos usar. O create-react-app foi configurado para executar este arquivo automaticamente antes de qualquer um de nossos testes, para que a Enzyme seja configurada corretamente.

O arquivo deverá conter o seguinte código:

Vamos começar!

Pergunte-se quais aspectos do componente que construímos podemos testar, nesse caso:

  • O que é mostrado inicialmente na tela
  • O valor deve aumentar ao clicar no botão

Agora, vamos ao arquivo src/App.test.js e mudar o seu conteúdo para:

Aqui estamos utilizando a renderização superficial (shallow) do Enzyme para testar o estado inicial da aplicação. Essencialmente, iremos renderizar apenas o código definido dentro daquele componente - qualquer coisa que seja importada de outro lugar não será incluída.

No código acima, a renderização shallow do nosso componente App é armazenada na variável wrapper criada no início do teste. Depois pegamos o texto dentro da tag h1 do componente e verificamos se é o mesmo texto passado dentro da função toContain

Vamos rodar esse teste para ver o resultado:

Vamos detalhar mais ainda o teste para melhor entendimento:

  1. Como falamos antes, o Jest nos permite criar um conjunto de testes.
  2. Às vezes você pode querer configurar algo antes que o teste aconteça. É por isso que estamos usando o beforeEach, então sempre antes de rodar os testes iremos renderizar o conteúdo que está dentro da função, neste caso estamos verificando se a renderização do nosso componente está acontecendo.
  3. Nosso primeiro teste é simples, estamos verificando se dentro do nosso componente que está envolto na variável wrapper, iremos encontrar (find) a tag h1 e o texto que está sendo renderizado inicialmente na aplicação é igual a 0. Simples, certo?

Testando a interação do usuário

Vamos escrever agora um novo teste que vai simular o clique do usuário no botão e então o nosso output será incrementado com 1.

Embaixo do primeiro teste adiciono o seguinte código:

Neste teste estamos usando a função simulate() para simular um click no botão. Nós também colocamos o que esperamos que aconteça após o clique, que o nosso texto na tag h1 que antes mostrava 0, agora seja igual a 1.

Dica: a maioria dos tipos de eventos pode ser simulada usando o método simulate() , incluindo inputs, cliques, focus, blurs, scrolls, etc.

Se você checar o seu terminal, você verá que os testes passaram como o esperado:

Agora, vamos fazer algo um pouco diferente! Nós vamos adicionar um teste para uma funcionalidade da aplicação que ainda não existe, e então escrever o código para esse teste passar. Aqui estamos aplicando e testando a metodologia do TDD - Test Driven Development.

Crie outro teste dentro da função describe() com o seguinte código:

Neste momento no terminal o erro já deve ter aparecido com a seguinte frase: Method "``simulate``" is only meant to be run on a single node. 0 found instead.

Não se assunte! Isso apenas indica que a o método simulate() foi chamado dentro de um elemento que ainda não existe.

Agora vamos no nosso componente App.js e escrever o código que vai fazer com que o nosso teste passe:

Note que mudamos a classe dos botões, criamos uma função decrement() e adicionamos um novo botão que irá diminuir o nosso counter em 1.

Neste ponto, todos os nossos testes devem ter passo com sucesso:

Estes foram alguns testes simples em um componente mas que nos traz uma base bem legal para continuar os estudos e aprender mais funções,  mais truques sobre as ferramentas de testes e vários outros tipos de testes que podemos criar. Lembrando que testes desse tipo podem ser aplicados em várias unidades da aplicação.

Bônus: testando um componente React com snapshot

Os testes de snapshot ajudam a verificar se a saída renderizada de um componente está correta em todos os momentos. Quando executamos um teste de snapshot, o Jest faz com que o componente React seja testado e armazena sua saída em um arquivo JSON.

Nos próximos teste, o Jest irá verificar se o output do componente não se desviou daquilo de foi salvo anteriormente. Caso você altere a saída do componente, o Jest o notificará e você poderá atualizar o teste para a versão mais recente ou corrigir o componente para que ele corresponda ao snapshot novamente.

Isso irá nos ajudar a evitar mudanças acidentais nos componentes, isso é muito importante principalmente quando estamos falando de desenvolvimento de grandes aplicação no frontend.

Para usar a feature de snapshot do Jest, precisamos de um pacote adicional, react-test-renderer, que podemos instalar da seguinte forma:

npm install react-test-renderer --dev

Após a instalação precisamos importar o renderer no início do nosso arquivo de testes App.test.js

import renderer from 'react-test-renderer';

Agora, crie o teste abaixo antes de todos os testes criados anteriormente:

A primeira vez que esse teste rodar ainda não existe nenhum snapshot para esse componente, então o Jest irá criá-lo. Você pode verificar o conteúdo do snapshot dentro da pasta src/__snapshots__

Abra o arquivo App.test.js.snap

Você pode ver que o conteúdo renderizado é o output do componente App e ficará salvo nesse arquivo. A próximo vez que o teste rodar, o Jest irá confirmar se os outputs são os mesmos.

Conclusão

Analisamos como o Jest torna o teste de componentes React muito mais fácil e dinâmico e como podemos usá-lo em conjunto com Enzyme para testes unitários e testes de snapshot.

Acredito que esta tenha sido uma boa introdução aos testes unitários no frontend, e espero que isso te leve a querer aprender cada vez mais sobre o assunto.

A cultura de testes podem parecer uma prática demorada e que requer muita responsabilidade, e a princípio, isso é verdade. Mas acredite, você acabará percebendo o quão relevante é testar uma aplicação e como isso irá lhe ajudar a debugar e estruturar do seu código, poupar tempo, reduzir o débito técnico, melhorar seu fluxo de trabalho e, aumentar sua produtividade a longo prazo.

Saber que temos um recurso que agrega confiabilidade ao nosso código que traz mais qualidade à aplicação é muito satisfatório, experimente!

Obrigada por lido até aqui e espero que este artigo tenha te ajudado de alguma forma 😊

⚠️
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.