Como aprender JS usando testes automatizados para isso
Eu quero trazer, sem enrolação, o modo como ajudo o pessoal que mentoro toda vez que alguém me vem com dúvidas sobre JS. No final, eu vou trazer uma explicação sobre os motivos de cada uma das sugestões, sugiro que leiam, mas deixarei pra depois caso vocês precisem voltar nesse post pra refazer alguns dos passos.
Alerta: vários comandos de terminal à frente.
Nota: vou ressaltar que esses passos vão funcionar bem pra sistemas Unix (Linux e Mac). Se você usa o Windows eu altamente recomendo aprender um pouco sobre o WSL (Windows Subsystem for Linux), eu inclusive tenho recomendado que esse seja o caminho pra eles, por trazer menos fricção quanto ao sistema operacional.
Instale o Node Version Manager (NVM)
Recomendo muito que você veja mais sobre a instalação aqui. Você vai precisar do curl
. Então talvez tenha que instalar esse pacote também. É simples, basta dar um Google. Ex: "como instalar curl no ubuntu 22.04" ou "como instalar curl no wsl do windows".
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
Feito isso, instale a versão mais recente estável (LTS - Long-term Support) do node com:
nvm install --lts
Instale o NPM (Node Package Manager)
Esse vai ser seu amigo por muito tempo como desenvolvedor JS. Bom começar a usá-lo o quanto antes, assim você vai aprendendo sobre ele aos poucos. Mais uma vez recomendo que veja mais sobre isso aqui.
npm install -g npm
Crie uma pasta para experimentar suas ideias
Se você quiser, não precisa criar essa pasta playground
, mas não vai fazer mal nenhum também.
No terminal, digite:
mkdir ~/playground
cd ~/playground
mkdir js-basics
cd js-basics
npm init -y
Esses comandos criam a pasta playground
, dentro dela a pasta js-basics
e inicia um repositório NPM dentro dessa pasta. O NPM durante a sua inicialização faz uma série de perguntas que te ajudam a construir esse arquivo, ao usar a flag -y
você pula esses passos e dá a resposta padrão pra todas essas perguntas.
Abra essa pasta no seu VSCode (vou assumir que você usa ele por ser o mais popular hoje em dia). Caso você tenha o comando do VSCode pra terminal, é só rodar um code .
caso contrário, é só abrir de dentro do VSCode mesmo.
Dentro desse projeto você vai ter um arquivo igual a esse:
{
"name": "js-basics",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Instalando o Jest
Jest é um famoso test runner. Ele vai rodar automaticamente os nossos códigos para que a gente não tenha que ficar preenchendo os campos manualmente todas as vezes. Dentro da pasta js-basics
digite:
npm install --save-dev jest
Isso vai adicionar o Jest como uma dependência do projeto. Em outras palavras, quem quiser ver esso projeto funcinando, vai ter que instalar esse pacote. A flag --save-dev
adiciona o Jest como uma dependência de desenvolvimento, o que faria com que ele nunca seja enviado para produção caso haja deploy. O porque disso a gente pode explicar em outro post, ou podem pesquisar por vocês mesmo ;)
Criando nosso primeiro teste
O jest executa um teste automatizado para todos os arquivos dentro de pastas chamada __tests__
ou __specs__
. Executa também arquivos em qualquer pasta com o nome tendo a extensão *.test.js
ou *.spec.js
.
Para que esses testes com o Jest sejam executados, precisamos adicionar um script no nosso arquivo package.json
.
...
"scripts": {
"test": "jest --watchAll"
}
...
Esse comando vai executar o Jest em todos os seus arquivos e ficar observando atualizações. Sempre que houver uma atualização salva em algum arquivo, os testes serão executados novamente.
Como queremos testar de maneira automatizada todos os arquivos, vamos criar uma pasta chamada __tests__
(são dois underlines de cada lado) na raiz do projeto.
Dentro da pasta __tests__
, vamos criar um arquivo helloWorld.js
.
Estratégia para criacão de testes
Vamos usar a estratégia de TDD (Test Driven Development) para escrever testes. Essa é uma boa estratégia, pois usa o teste para dizer o que seu código deveria fazer e só então escrevemos o código buscando o resultado. Isso também traz a vantagem de nos forçar pensar como queremos que nosso código opere antes de começarmos a escrevê-los.
A estratégia é basicamente a seguinte:
- Escrevemos um teste que sabemos que vai falhar (red)
- Escrevemos código até que o teste passe (green)
- Refatoramos, se houver espaço pra isso
De maneira gráfica, esse fluxo se parece com isso:
Dentro do arquivo helloWorld.js
vamos escrever o nosso primeiro teste.
// helloWorld.js
it('should say hello world', function () {
expect(sayHelloWorld()).toBe('Hello World');
});
A função it
cria a nosso teste automatizado. Esse teste lê-se it should say hello world
(esse deveria dizer hello world). Essa string representa o que você vai ver no console quand o teste for executado e vai ser por ele que você vai saber o que está falhando nos seus testes.
O expect
guarda o resultado daquilo que você quer executar. Nesse caso, o expect
espera o resultado da execução da função helloWorld
. O que quer que essa função retorne, esse será o valor do expect
.
Essa função é encadeada com um .toBe
que diz qual deveria ser o valor que essa função deve retornar se tudo der certo.
Vendo nosso primeiro teste rodando:
npm run test
Esse comando vai rodar o script test que modificamos no nosso package.json
. E veremos que o nosso teste está falhando (red).
FAIL __tests__/helloWorld.js
✕ should say hello world
● should say hello world
ReferenceError: sayHelloWorld is not defined
1 | it("should say hello world", () => {
> 2 | expect(sayHelloWorld()).toBe("Hello World");
| ^
3 | });
4 |
at Object.expect (__tests__/helloWorld.js:2:3)
Aprendendo a ler a mensagem de erro
Ver esse tipo de erro pela primeira vez pode ser assustador. Principalmente, se você é do tipo que não toma o tempo pra isso.
FAIL __tests__/helloWorld.js
: o arquivo helloWorld.js
tem testes que estão falhando.
✕ should say hello world
: esse teste está falhando (o ✕
indica isso).
● should say hello world
ReferenceError: sayHelloWorld is not defined
Esse teste está falhando com o erro ReferenceError
porque sayHelloWorld
não está definido.
1 | it("should say hello world", () => {
> 2 | expect(sayHelloWorld()).toBe("Hello World");
| ^
3 | });
4 |
O erro se encontra na linha 2.
Com todas essas informações em mãos podemos começar a corrigir o erro definindo a função sayHelloWorld
.
// helloWorld.js
function sayHelloWorld() {}
it('should say hello world', function () {
expect(sayHelloWorld()).toBe('Hello World');
});
Ao salvarmos o arquivo o erro muda:
expect(received).toBe(expected) // Object.is equality
Expected: "Hello World"
Received: undefined
O teste esperava que o valor retornado da função fosse Hello World
, mas ao invés disso, o valor retornado foi undefined
. Bora corrigir.
function sayHelloWorld() {
const message = "Hello World";
return message;
}
it("should say hello world", () => {
expect(sayHelloWorld()).toBe("Hello World");
});
Quando salvamos o teste, nosso console mostra o sucesso da execução o teste da seguinte forma:
PASS __tests__/helloWorld.js
✓ should say hello world (1 ms)
Nesse momento, nosso teste já passou pelas fases de 1 e 2 (red e green). Falta o refactor. Essa função não precisa da variável message
só pro retornar seu valor. Podemos fazer isso de maneira direta.
function sayHelloWorld() {
return "Hello World";
}
it("should say hello world", () => {
expect(sayHelloWorld()).toBe("Hello World");
});
Refatoração feita e nosso teste continua passando.
Porque eu decido ir por esse caminho com meus mentorados
No final do dia, quando resolvemos problemas com javascript estamos apenas escrevendo uma sequência de funções. Ficar bom em dar nomes para funções e variáveis, entender de estrutura de dados e algoritmos é o que vai te fazer apto a criar sistemas complexos no futuro.
Testes automatizados também vão ser parte integrante do dia a dia de um bom desenvolvedor. Crescer acostumado a isso é um bônus.
Fazer dessa maneira, também te permite brincar com soluções diferentes para o mesmo problema o que vai aumentar o seu repertório com o tempo.
Agora é com você! Continue com os desafios, vai com bastante mão na massa, crie princípios sólidos e você vai vencendo um desafio de cada vez.
Próximos passos
Aproveite o boom de apps como o Chat GPT à seu favor. Peça exercícios para praticar como:
- Quero 10 exercícios com o tema manipulação de string
- Quero 15 exercícios com o tema de arrays
- Quero 5 exercícios com o tema requests HTTP
Além disso, se seu teste não está funcionando e você está sem idea do porque, cole seu código lá e pergunte, "Por que esse teste não funciona?"
e use a ferramenta para dar um boom no seu conhecimento.
Uma vez habituado a resolver os mais variados tipos de função a ideia é ir aumentando o tamanho dos problemas e ainda assim ser capaz de testá-los.
Aqui vai alguns exemplos de exercícios que você pode começar a fazer agora!
- Escreva uma função que calcule a soma de dois números e retorne o resultado.
- Escreva uma função que determine se um número é par ou ímpar e retorne "even" ou "odd".
- Escreva uma função usando um laço for, crie e retorne uma string que tenha os números de 1 a 10 separados por espaço.
Se tiver algo aqui que não funcione ou algum tema que gostaria de ver explicado com mais profundidade, deixe um comentário. Espero que esse possa ser um bom ponto de partida para você se tornar um grande dev.