Introduzindo Testes Unitários para Devs Iniciantes

Weslley Araújo
Desenvolvedor ativo no ecossistema open source, Microsoft MVP, mantenedor do MySQL2 e criador do Poku ✨

Introduzindo Testes Unitários para Devs Iniciantes

Sumário

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:

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 🧙🏻


📋 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 🧑🏻‍🎓
informação

Esse artigo foi postado icialmente no TabNews.