Pular para o conteúdo principal

Introduzindo Testes Unitários para Devs Iniciantes

· Leitura de 7 minutos
Weslley Araújo
Desenvolvedor ativo no ecossistema open-source, co-mantenedor do MySQL2 e criador do Poku ✨

Banner

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.

💡 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.


🧑🏻‍🎓 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');
Erro de asserção
  • 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

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');
Asserção com sucesso

🧪 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) seja false.
  • ✅ 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) seja true.
  • 📝 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:

Exemplo de Sucesso com o Poku
  • 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 foi minha primeira "aula" em formato de blog. Feedbacks são sempre bem vindos 🩵
  • Muitos termos são explicados repetidamente em momentos diferentes, isso é uma escolha didática, mas podem falar caso tenha ficado cansativo 🧑🏻‍🎓
info

Esse artigo foi postado icialmente no TabNews.