Executando verificação de segurança...
26

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');
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 é 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 🧑🏻‍🎓
5
3
3

Você coneguiu me explicar de maneira simples em um post o que eu não consegui aprender na faculdade hahaha. Como um dev iniciante, eu agradeço muito e de quebra vou usar sua lib em projetos pessoais, achei muito simples e intuitiva.

2

@patrick, é muito bom (e como é, haha) poder ler esses feedbacks (de todos os comentários que fizeram).

Nosso porquinho brasileiro ainda tem uma longa jornada pela frente 🇧🇷✨

3

Com essas suas postagens eu vou acabar ficando sem TabCoins kkkkkk

Mas parabéns pela explicação e eu COM CERTEZA quero muito um post sobre testes de integração e E2E (que sinceramente eu nunca fiz e gostaria de ler mais sobre).

E sobre sua didática, achei bem fácil de entender e gostei bastante do jeito que escreveu.

1

Valeu @hiroshimorowaka 🚀

Eu fiquei pensando sobre essa publicação. Por quase 9 dias consecutivos ela permanceceu na página inicial e todas as interações foram positivas 💙

Acredito que esse conteúdo possa ir além se for no formato de um vídeo educacional bem criativo e com uma edição moderna.

É um investimento que super quero fazer, inclusive já iniciei uma pesquisa com programadores que possuem experiência na área e nunca fizeram testes automatizados por algum motivo (complexidade, empresa que não usa, pessoas que acreditem que não é realmente necessário, etc.).

Com esse "curso" gratuito (provavelmente no YouTube), eu espero conseguir trasmitir a filosofia do Poku de uma forma ainda mais forte, mostrando que é possível criar testes automatizados em qualquer nível e que os testes podem sim ser simples.

2

Nossa, pra mim vídeos são mais fáceis de digerir do que texto devido a algumas limitações que eu tenho e eu ficaria muito agradecido se você fizesse isso.

Eu até pouco tempo atrás negligênciei os testes automatizados e achava que "bom, se eu cliquei no botão e ele funcionou, com certeza deve estar funcionando pra todo mundo"... Ledo engano, o tanto de problema que eu já tive que arrumar por causa disso não ta escrito, eu estava lutando contra as regressões que eu mesmo estava introduzindo.

Eu super entendo porque as pessoas iniciantes nos testes pensam que isso atrasa o projeto. Sim, de fato atrasa mesmo você terminar a feature, mas isso economiza o triplo do tempo que "atrasou" porque você não vai precisar debuggar algo que já funcionava, entende?

Escrever testes consome sim bastante tempo, principalmente se quiser fazer um coverage alto, mas economiza tempo dos próximos programadores e seu próprio tempo 3x mais.

Vou finalizar aqui parafraseando o aposentado (do youtube) Fabio Akita:

"Se você não escreve testes, você vai introduzir regressão e vai perder 3 vezes mais tempo arrumando o que já funcionava do que escrevendo os testes que deveria [...] "

"Você não escreve código pra máquina, você escreve código para as outras pessoas, inclusive você do futuro"

3
2

Caramba, eu li isso no mesmo dia que estava pensando em aprender jest! Cara, obrigado mesmo! Eu com certeza irei usar o poku em alguns projetos na empresa, poku é Brasil!