Escrevendo testes automatizados mais legíveis e práticos com Jest e Node.js
Opa, tudo bem? Vim dividir um pouco de algumas práticas que podem parecer bobas a primeira vista, mas que pode te poupar boas dores de cabeça procurando bugs e refatorando código.
Criar testes no começo é bem confuso, entender tanto os benefícios quanto como escrever e tornar desde o código dos testes em si mais legíveis, tal qual como tornar logs e outros facilitadores mais úteis.
Vou deixar alguns tópicos separados caso queira rever ou pular partes, então vamos ao que interessa!
- Configuração e estrutura da nossa API Node.js
- Testes de integração e unidade
- AAA pattern
- Demonstre os requerimentos, expectativas e possiveis regras de negocio na descrição dos seus testes
- Ganhos diretos e indiretos dessas abordagens
- Conclusão
- Referências
Configuração e estrutura da nossa API Node.js
Como nosso objetivo é falar sobre testes, nossa aplicação não faz nada mais além de criar uma abstração de criação de usuário e a partir disso criar duas rotas pra fazer essas atividades.
Vamos testar tanto as possiveis requisições que a API poderia receber, quanto a nossa abstração de um banco de dados registrando um usuário. Pra isso, vamos começar instalando uns pacotes.
Depois de criar uma pasta pro nosso projeto, na raiz do mesmo execute um npm init -y
para inicializar o nosso instalador de pacotes, em seguida, vamos instalar tudo análogo ao funcionamento da api:
npm i -S express body-parser cors
E vamos instalar mais algumas bibliotecas apenas pro ambiente de desenvolvimento com o adendo que o nodemon só está ai pra caso queira usar alguma ferramenta pra testar manualmente nossas rotas:
npm i -S -D jest supertest nodemon
O Jest é a biblioteca pra usaremos pra fazer os testes, tal que o supertest é usado pra conseguir requisitar um recurso da nossa API tal qual um client faria, como poderá ver mais a frente.
No app.js temos apenas a configuração básica pra servir nossas rotas, nada demais.
Já as rotas somente usam nosso controller e informa quais requisições devem ser feitas.
Nosso controller coleta as informações vindas da requisição, e chama nossa abstração do banco de dados pra criar ou recuperar um usuário.
Nossa abstração é bem simples, somente acrescentando um usuário a um array comum.
E por fim nosso server.js que somente inicia o servidor da aplicação.
Testes de integração e unidade
Antes de escrevermos nossos código, é importante entender a diferença entre testes de intregração e unidade, e vamos escrever ambos.
Testes de unidade tem a finalidade de cobrir partes muito específicas ou mínimas do seu sistema: uma função que soma dois números, uma função que cria um token, o método da classe que cria um registro no banco.
Desta maneira vamos escrever testes pra cobrir nossa abstração de banco de dados e garantir que ela está funcionando como deveria, e retornando erro quando informações erradas são passadas.
Testes de integração tem a finalidade de testar partes que requisitam diferentes módulos do sistema, no nosso caso, vamos escrever testes pra cobrir nossas requisições HTTP inteiras, verificando desde o status de resposta ao formato da resposta da mesma.
AAA pattern
Quando vamos escrever testes, pretendemos simular situações reais que nosso sistema enfrentará, e assumindo esse contexto, precisaremos preparar o cenário, requisitar o recurso, e validar o retorno do mesmo.
Exatamente pensando nessa separação que o AAA pattern propoem que essas partes estejam muito bem distintas, dado que:
Primeiro A (arrange/organizar): será onde você irá preparar todas as configurações necessárias pra requisitar o recurso, seja requisitar alguma informação a um serviço externo, usar pseudo usuarios, requisições a bancos de dados. Tudo que precisará estar pronto pra utilizar o módulo.
Segundo A (act/ato): a utilização do módulo em si, seja uma requisição HTTP, gravar um documento no banco, ou qualquer que seja o processo envolvido.
Terceiro A (assert/afirmar): iremos validar o retorno do módulo. Deveria retornar a soma entre 2 e 3? O status de resposta deveria ser 200? E o formato do objeto de retorno? Aqui iremos ver se tudo está como deveria estar.
E então como exemplo vamos usar esse teste da nossa abstração do banco de dados. E, como sugerido, preparamos os dados, então requisitamos o recurso e em seguida validamos se o retorno é o esperado. A propósito, este seria um teste de unidade.
Demonstre os requerimentos, expectativas e possiveis regras de negócio na descrição dos seus testes
É importante deixarmos claro a intenção e o que vamos testar, além de demonstrar desde o que é necessário ao que esperamos de retorno. E sim, isso na descrição dos nossos testes. Vamos separar em três partes:
- O que vai ser testado: rotas de usuarios-criar usuarios, metodo de pagamento-efetuar pagamento, etc.
- Cenário: o contexto do teste, por exemplo, criar um usuário informando email e senha, efetuar um pagamento com cartão de débito, etc.
- Expectativa: ao criar um usuário retorna uma mensagem de sucesso, os dados de confirmação da compra, etc.
Aqui atendemos o primeiro critério descrito nos dois describe em seguida descrevemos o que informamos e o que esperamos do teste.
Observe que antes mesmo de ler o código do teste você já consegue entender que recurso irá ser utilizado, o que é necessário pro mesmo, além do que esperamos. E, como pode perceber, utilizamos o AAA pattern comentado anteriormente.
Ganhos diretos e indiretos dessas abordagens
O primeiro ganho é testarmos toda nossa aplicação, parece óbvio(ou não), mas apesar de parecer ser um retrabalho, escrever bons testes facilitam não somente o desenvolvimento do sistema em si como também a manutenção e desenvolvimento de novas features, permitindo garantir que o funcionamento do que já foi feito não seja ameaçado.
Outra coisa interessante são os logs, dado como descrevemos nossos testes, fica perfeitamente simples entender onde está o código do teste quando alguma coisa inexperada acontece:
Como podemos perceber, agora conseguimos entender o arquivo, recurso, o que foi informado e o que não foi retornado como esperado, tudo isso pelo log!
Conclusão
Como já conversamos, é isso pode te salvar de longas horas tentando descobrir como e o que ta quebrando sua aplicação, quais recursos não foram passados e testes que ao ler não são tão claros.
O ganho fica principalmente na identificação e explicitação de cada processo que o sistema irá passar, tendo ganhos a curto e longo prazo.
No mais, caso queira ver o repositorio de base deste artigo, segue o link: https://github.com/mugarate12/TestsNodeJs
Referências
Esse artigo tem bem mais conteúdo e é incrível, dele trouxe algumas observações que estão presentes nesse artigo. Se você lê em inglês, cabe uma ótima leitura: https://medium.com/@me_37286/yoni-goldberg-javascript-nodejs-testing-best-practices-2b98924c9347