Tutorial: Introduzindo Testes Unitários para Devs Iniciantes (JS) 🧪
💡 Motivação
Se você é um desenvolvedor iniciante (ou não), eu quero te mostrar que testes podem sim ser simples e que a complexidade só vem conforme nossa própria necessidade, não do tester.
Transparência: Esse tutorial pode ser visto como um pitch indireto da minha postagem anterior, mas o foco aqui é não apenas ensinar, como também introduzir desenvolvedores no "mundo dos testes" (especialmente os iniciantes) 🙋🏻♂️
🧑🏻🎓 Introdução ao Mundo dos Testes
Imagine um sistema onde vários usuários tiveram a conta invadida porque criaram senhas como "1234
" e o sistema permitia, mesmo tendo uma validação para isso, mas sem garantia nenhuma que essa função estava realmente funcionando 🥲
Isso facilmente seria evitado se existissem testes garantindo que a função que valida a senha funcionasse como deveria sempre que uma modificação é feita no sistema, concorda? 🔐
Mas quando um dev iniciante procura por testes automatizados, isso pode parecer (ou até ser) complexo:
- Os testers podem exigir configurações de ambiente (especialmente quando se trata de ESM e TypeScript)
- Podem mudar o comportamento do ambiente de desenvolvimento
- Podem exigir que você adapte seu código para funcionar com eles
- E até que você tenha um conhecimento previamente aprofundado sobre eles (documentação) para simplesmente iniciar
📚 Escolhendo o Tester
Atualmente, existem muitos testers, alguns focados em boas práticas, outros focados na produtividade, performance e por aí vai.
Alguns dos mais populares:
- Jest
- Mocha + Chai
- Vitest
Para esse tutorial, vamos usar um tester que eu criei (Poku), devido sua simplicidade, mas você pode usar qualquer um 🚀
📦 Instalando nosso Tester
npm i -D poku
🧪 Por que "Unitários"?
Ao desenvolvermos nossos códigos, é comum dividirmos tarefas pequenas em funções menores, então exportamos essas funções para usá-las em vários lugares no nosso código, certo?
Criar os testes unitários garante que essas "funções unitárias" sempre funcionem como esperado 🧙🏻
Se essa postagem for positiva, eu também gostaria de fazer um tutorial igualmente simples para testes de integração e e2e, focados para desenvolvedores iniciantes 🤝
📋 Criando um Projeto Simples
Vamos elaborar um projeto bem simples, onde iremos apenas validar uma senha.
Nosso projeto precisa validar se a senha passada:
- ✅ É uma string
- ✅ Possui ao menos oito caractéres
- ✅ Possui ao menos uma letra maiúscula
- ✅ Possui ao menos uma letra minúscula
- ✅ Possui ao menos um número
Se a senha for válida, devemos retornar true
, caso contrário, false
.
Para isso, iremos criar o arquivo:
- src/validations.mjs
export const validatePassword = (password) => {
// Verifica se a senha é uma string
if (typeof password !== 'string') return false;
// Verifica se a senha possui 8 caractéres ou mais
if (password.trim().length < 8) return false;
// Verifica se a senha possui ao menos uma letra maiúscula
if (!/[A-Z]/.test(password)) return false;
// Verifica se a senha possui ao menos uma letra minúscula
if (!/[a-z]/.test(password)) return false;
// Verifica se a senha possui ao menos um número
if (!/[0-9]/.test(password)) return false;
// Retorna verdadeiro se todas as validações acima passarem
return true;
};
Visando manter o exemplo simples, não vamos ir muito além disso 🧙🏻
- Não use essa função para códigos reais, o intuito é ser simples para focar no apredizado 🧑🏻🎓
- Você pode trocar de src/validations.mjs para src/validations.js usando o
"type": "module"
no seu arquivo package.json 🧙🏻
🧑🏻🔬 Entendendo os Testes
Teoricamente, nossa função do projeto já funciona, esse é o momento que a gente começa fazer vários console.log
, certo? Nada disso 😂
Vamos tornar o que seriam esses "console logs", em testes automatizados que sempre serão executados quando o projeto passar por uma alteração 🚀
Como? Usando asserções 👇🏻
☑️ O que são Asserções?
Nos testes, asserções são usadas para garantir que um resultado é realmente o que a gente espera.
Cada tester pode ter uma forma diferente de fazer isso, mas o final costuma ser o mesmo:
- Se nossa verificação (asserção) não for exatamente como o esperado, o teste irá disparar um erro nessa asserção.
📝 Exemplo:
Imagine que 1 + 1
precisa retornar 2
, mas retornou "11"
.
Quando chegar na asserção que espera o resultado 2
, ela irá disparar um erro no teste dizendo que esperava 2
(número), mas recebeu "11"
(string):
import { assert } from 'poku';
assert.strictEqual('1' + '1', 2, '1+1 deve retornar 2');
actual
:- "11" — É o retorno dinâmico (soft code) da nossa função ou variável
expected
:- 2 — é o valor bruto (hard code) que deve ser retornado pelo
actual
- 2 — é o valor bruto (hard code) que deve ser retornado pelo
Tanto o Poku como o próprio Node.js possuem o método
assert
com a forma de uso exatamente iguais 🧙🏻Quanto ao funcionamento, o Poku oferece uma forma simples e inteligente de executar múltiplos arquivos e descreve todas as asserções que passaram ou não no terminal 🐷
📝 Corrigindo nosso exemplo:
import { assert } from 'poku';
assert.strictEqual(1 + 1, 2, '1+1 deve retornar 2');
🧪 Criando os Testes
Finalmente a parte boa 🎉
Para isso, vamos usar a criatividade e gerar:
- ❌ Várias senhas inválidas para simular o comportamento tanto de usuários usuais, como de usuários mal intencionados
- Nas senhas inválidas, nós esperamos (
expected
) que o resultado (actual
) sejafalse
.
- Nas senhas inválidas, nós esperamos (
- ✅ Senhas válidas para garantir que nossa função entende que a senha deve ser válida quando passar por todos os critérios
- Nas senhas válidas, nós esperamos (
expected
) que o resultado (actual
) sejatrue
.
- Nas senhas válidas, nós esperamos (
- 📝 Nada de comentários no teste, vamos descrever o que é cada um na própria asserção (
message
)
Vamos criar nosso arquivo de teste:
- test/password.test.mjs
import { assert } from 'poku';
import { validatePassword } from '../src/validations.mjs';
assert.strictEqual(
validatePassword(),
false,
'Valida se a senha não for passada'
);
assert.strictEqual(
validatePassword(12345678),
false,
'Valida se a senha não for uma string'
);
assert.strictEqual(
validatePassword(''),
false,
'Valida se a senha for uma string vazia'
);
assert.strictEqual(
validatePassword('abcd1234'),
false,
'Valida se a senha não possuir ao menos uma letra maiúscula'
);
assert.strictEqual(
validatePassword('1234EFGH'),
false,
'Valida se a senha não possuir ao menos uma letra minúscula'
);
assert.strictEqual(
validatePassword('abcdEFGH'),
false,
'Valida se a senha não possuir ao menos um número'
);
assert.strictEqual(
validatePassword('abcdEF12'),
true,
'Valida se a senha for válida'
);
- Você pode trocar de test/password.test.mjs para test/password.test.js usando o
"type": "module"
no seu arquivo package.json 🧙🏻
🔬 Verificando se os Testes Passaram
npx poku
- Ao executar o comando
npx poku
, por padrão, o tester irá procurar por todos os arquivos com a extenção*.test.*
ou*.spec.*
no diretório em que o comando foi executado 🧙🏻
E finalmente, nosso resultado:
- A primeira saída mostra em qual diretório o Poku está procurando por testes
- A segunda saída mostra o arquivo que está sendo testado no momento
- Dentro de cada arquivo de teste, ele irá mostrar todas as asserções que possuírem uma mensagem
- Ao finalizar, seja com sucesso ou erro, ele irá mostrar o comando que ele executou para o arquivo testado:
node test/password.test.mjs
- Quando todos os arquivos de testes terminarem, ele irá mostrar um resumo de quantos arquivos passaram e/ou falharam
- No final, o código de saída será:
- ✅
0
para sucesso - ❌
1
para falha
- ✅
💭 Conclusão
Com os testes unitários que criamos, nós garantimos não só que nossa função funcione como deveria, como inclusive prevemos como nosso projeto reage em situações atípicas antes que elas aconteçam 🔐
Espero ter te provado que testes podem sim (!) ser simples 🧑🏻🎓
- Essa é minha primeira "aula" em formato de blog. Feedbacks são sempre bem vindos 🩵
- Viu algum erro? Fique à vontade para pontuar e eu corrigirei 🤝
- Muitos termos são explicados repetidamente em momentos diferentes, isso é uma escolha didática, mas podem falar caso tenha ficado cansativo 🧑🏻🎓