Vamos falar sobre testes?

Criado por Adriano de Azevedo usando reveal.js

Antes de começarmos

Desculpa por não conseguir trazer essa apresentação semana passada

Essa apresentação está cheia de memes,

Não leve tudo a sério

Eu queria escutar um pouco vocês

Vamos abrir o coração gente ❤️

Por que ainda estamos enviando coisas novas para produção sem teste?

Por que criei essa apresentação?

As dúvidas realmente são parecidas

E as vezes eu não consigo responder todo mundo

Um pouco sobre o Jest

Escrever testes para Front-end sempre foi um caos desgraçado. Nunca existiu um padrão, ou uma ferramenta boa e simples de se configurar. Muitas vezes, testes para front-end eram deixado de lado pela falta de clareza de como se fazer.
Veja detalhes aqui

O que faz do Jest uma ótima ferramenta para escrever testes hoje em dia?

Jest é um framework de teste em JavaScript [...] Ele permite que você escreva testes com uma API acessível, familiar e rica em recursos [...] está bem documentado, requer pouca configuração [...]

Jest torna os testes agradavéis.
Veja detalhes aqui

O jest é uma ferramenta completa

Ferramentas para isso (testar) sempre estiveram por aí, tais como QUnit, Jasmine, Mocha, Karma, Chai, Sinon [...] fazer todas essas ferramentas trabalharem juntas, olha, levava dias para deixar do jeito certo [...]
Veja detalhes aqui

Nós já usamos o Jest aqui na empresa ❤️

Vamos relembrar a anatomia de um teste

Todo teste precisa ter

  • Um nome descritivo
  • Algo que precisa ser testado

O nome do arquivo de teste por padrão, precisa seguir essa convenção:

              
                (/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$
              
            
              
                ├── __tests__
                │   └── component.spec.js # test
                │   └── anything # test
                ├── package.json # not test
                ├── foo.test.js # test
                ├── bar.spec.jsx # test
                └── component.js # not test
              
            

Quando estamos escrevendo testes, geralmente precisamos verificar se os valores satisfazem certas condições. O Jest faz isso por meio da função `expect` usando "matchers"

Usando um "matcher"

              
              test('dois mais dois é quatro', () => {
                expect(2 + 2).toBe(4);
              });
              
            

Podemos testar o oposto de um "matcher"

              
              test('dois mais dois não é 5', () => {
                expect(2 + 2).not.toBe(5);
              });
              
            

O Jest tem vários "matchers"

Cada "matcher" é usado para testar valores de maneiras diferentes

Nós podemos também adicionar os nossos próprios "matchers"

Alguns exemplos da documentação:

                
                  test('atribuição de objeto', () => {
                    const data = {one: 1};
                    data['two'] = 2;
  
                    // devemos usar toEqual para testar objetos
                    // toBe utiliza `Object.is` para
                    // testar a igualdade extata.
                    expect(data).toEqual({one: 1, two: 2});
                  });
                
              
                
                  test('nulo', () => {
                    const n = null;

                    expect(n).toBeNull();
                    expect(n).toBeDefined();
                    expect(n).not.toBeUndefined();
                    expect(n).not.toBeTruthy();
                    expect(n).toBeFalsy();
                  });
                
              
                
                  test('dois mais dois', () => {
                    const value = 2 + 2;
                    expect(value).toBeGreaterThan(3);
                    expect(value).toBeGreaterThanOrEqual(3.5);
                    expect(value).toBeLessThan(5);
                    expect(value).toBeLessThanOrEqual(4.5);
                  
                    // toBe e toEqual são equivalentes para números
                    expect(value).toBe(4);
                    expect(value).toEqual(4);
                  });
                
              

Ou seja:

Os testes podem:

Ser agrupados

                  
                    describe("2 + 2", () => {
                      test('dois mais dois é quatro', () => {
                        expect(2 + 2).toBe(4);
                      });
      
                      test('dois mais dois não é 5', () => {
                        expect(2 + 2).not.toBe(5);
                      });
                    });
                    
                

Ser desabilitados

                  
                    describe("2 + 2", () => {
                      test.skip('dois mais dois é quatro', () => {
                        // teste quebrado de propósito
                        expect(2 + 2).toBe(5);
                      });
      
                      test('dois mais dois não é 5', () => {
                        expect(2 + 2).not.toBe(5);
                      });
                    });
                    
                

Setup e Teardown

Muitas vezes ao escrever testes você tem algum trabalho de configuração que precisa acontecer antes ou depois de executar testes

  • beforeAll
  • beforeEach
  • afterEach
  • afterAll

Sintaxe

              
              // executa em todos os testes do arquivo
              beforeEach(() => {
                initializeCityDatabase();
              });
              
              test('city database has Vienna', () => {
                expect(isCity('Vienna')).toBeTruthy();
              });

              describe('matching cities to foods', () => {
                // executa apenas para testes neste bloco
                beforeEach(() => {
                  initializeFoodDatabase();
                });
              
                test('Vienna <3 sausage', () => {
                  expect(
                    isValidCityFoodPair('Vienna', 'Wiener Schnitzel')
                  ).toBe(true);
                });
              });
              
            

Ordem de execução

              
              beforeAll(() => console.log('1 - beforeAll'));
              afterAll(() => console.log('1 - afterAll'));
              beforeEach(() => console.log('1 - beforeEach'));
              afterEach(() => console.log('1 - afterEach'));
              test('', () => console.log('1 - test'));
              
              // 1 - beforeAll
              // 1 - beforeEach
              // 1 - test
              // 1 - afterEach
              // 1 - afterAll
              
            

Entendendo o code coverage

O que é? 🤔

[...] uma métrica quantitativa, visa medir quanto (%) do software é coberto/exercitado ao executar um determinado conjunto de casos de testes.
Veja detalhes aqui

Como isso se parece? 🤔

Tipos básicos de cobertura de código 📖

Function

verifica quantas funções do código são chamadas

Statement

verifica quantas instruções do código são executadas

Branch

verifica se cada ramificação de cada estrutura de controle (incluindo if/else, switch case, for, while) é executada

Condition

verifica se cada sub-expressão booleana são avaliadas ambas como verdadeiras e falsas

Ou seja, coisas como isso afetam o coverage e precisam ser testadas

              
                
              
            

Evite escrever funções grandes

quanto mais responsabilidade existir em uma função, mais dificil vai ser para testar

⚠️⚠️⚠️ Atenção ⚠️⚠️⚠️

não há garantia que o código será livre de defeito por ter uma cobertura alta

⚠️⚠️⚠️ Atenção ⚠️⚠️⚠️

cobertura alta de código não é approve automático de PR

  • Os casos de teste que foram escrito fazem sentido?
  • Está faltando algum cenário ser contemplado?

Estão curiosos para saber o coverage do E-commerce?

Isso mesmo, está baixa

Como aumentar a cobertura do E-commerce?

Isso mesmo,

escrevendo testes

Estratégia de adoção gradual

Aumentar a cobertura nem sempre é uma tarefa fácil e assim como a adoção do TypeScript, podemos fazer aos poucos

Existem ferramentas que podem nos ajudar nisso

Como?

O Codecov faz um análise estática no nosso coverage e conseguimos tirar informações relevantes disso

Nós podemos, por exemplo, bloquear a CI e evitar que o PR seja mergeado caso ele não atenda um valor (target) de coverage do código novo

Com isso nós "forçamos" que novas features sejam entregues minimamente testadas

Como configurar o Jest em um projeto?

Para fins didáticos, vamos criar um projeto do zero

              
              # inicia um novo projeto
              npm init -y

              # adiciona o jest como dependência do projeto
              npm install --save-dev jest
              
            

Adicione a seguinte seção no package.json

              
              {
                "scripts": {
                  "test": "jest"
                }
              }
              
            

Nosso primeiro teste

              
              // src/sum.js
              function sum(a, b) {
                return a + b;
              }

              module.exports = sum;
              
            
              
              // src/sum.test.js
              const sum = require('./sum');

              test('adds 1 + 2 to equal 3', () => {
                expect(sum(1, 2)).toBe(3);
              });
              
            

Como executar os testes?

              
              # execute no seu terminal
              npm run test

              # PASS  src/sum.test.js
              # ✓ adds 1 + 2 to equal 3 (2 ms)              
              
            

Até que é simples, não é mesmo?

Você deve estar pensando 🤔

  • ok, é simples. Mas como faço para testar um código de verdade?
  • A minha aplicação é muito mais complexa do que isso.
  • Por que no exemplo foi usado module.exports e require?
  • Cade o React?
  • Cade o Typescript?

Vamos avançar

Como utilizar import e export?

Vamos alterar nosso código

              
              // src/sum.js
              function sum(a, b) {
                return a + b;
              }

              export default sum;
              
            
              
              // src/sum.test.js
              import sum from './sum';

              test('adds 1 + 2 to equal 3', () => {
                expect(sum(1, 2)).toBe(3);
              });
              
            

Surprise

Existem várias formas de corrigir isso...

Neste exemplo vamos usar o Babel 🤓

O que é o babel e por que ele é necessário?

A gente gosta do JavaScript,

mas temos que conviver com os problemas dele

Por meio de plugins, o babel transpila código JavaScript moderno em uma versão compatível com versões anteriores do JavaScript em navegadores ou ambientes atuais e mais antigos.

Esse é o babel

Instalando as dependências necessárias

              
              # adiciona o babel como dependência do projeto
              npm install --save-dev babel-jest 
              npm install --save-dev @babel/core 
              npm install --save-dev @babel/preset-env
              
            

Configurando o babel

              
              // babel.config.js
              module.exports = {
                presets: [
                  ['@babel/preset-env', {targets: {node: 'current'}}]
                ],
              };
              
            

Vamos rodar de novo?

              
              # execute no seu terminal
              npm run test

              # PASS  src/sum.test.js
              # ✓ adds 1 + 2 to equal 3 (2 ms)              
              
            

Como utilizar TypeScript?

Mais dependências

              
              # adiciona suporte ao TypeScript via babel
              npm install --save-dev @babel/preset-typescript
              npm install --save-dev @types/jest
              npm install --save-dev @types/node
              
            

Configurando o babel

              
              // babel.config.js
              module.exports = {
                presets: [
                  ['@babel/preset-env', {targets: {node: 'current'}}],
                  '@babel/preset-typescript'
                ],
              };
              
            

Vamos alterar nosso código

              
              // src/sum.ts
              function sum(a: number, b: number) {
                return a + b;
              }
              
              export default sum;              
              
            
              
              // src/sum.test.ts
              import sum from './sum';

              test('adds 1 + 2 to equal 3', () => {
                expect(sum(1, 2)).toBe(3);
              });
              
            

Vamos rodar de novo?

              
              # execute no seu terminal
              npm run test

              # PASS  src/sum.test.ts
              # ✓ adds 1 + 2 to equal 3 (2 ms)              
              
            

⚠️⚠️⚠️ Atenção ⚠️⚠️⚠️

O suporte para TypeScript via Babel é apenas para transpilação.

O Jest não irá checar a tipagem dos seus testes enquanto eles são executados

Como testar componentes React

usando apenas os recursos nativos?

Mais dependências

              
              # adiciona as dependências do React
              npm install --save react
              npm install --save react-dom
              npm install --save-dev @babel/preset-react

              # adiciona suporte ao TypeScript
              npm install --save-dev @types/react
              npm install --save-dev @types/react-dom
              
            

Configurando o babel

              
              // babel.config.js
              module.exports = {
                presets: [
                  ['@babel/preset-env', {targets: {node: 'current'}}],
                  '@babel/preset-typescript',
                  '@babel/preset-react'
                ],
              };
              
            

Adicionando primeiro componente

              
                
              
            

Como vou renderizar meu componente se no Nodejs não tem window?

JSDOM

É usado pelo Jest para emular um navegador web durante a execução dos testes.

Testando nosso componente

              
                
              
            

Usando React Testing Library

Mais dependências

              
              # dependencias necessarias para usar React Testing Library
              npm install --save-dev @testing-library/jest-dom
              npm install --save-dev @testing-library/react
              npm install --save-dev @testing-library/user-event
              
            
              
                
              
            
              
                // jest-setup.ts
                import '@testing-library/jest-dom';
              
            
              
                
              
            

"Eu uso create-react-app e nunca precisei fazer tudo isso"

Dúvidas frequentes sobre o jest

Como rodar os testes em modo watch?

              
              # se você estiver usando o npm
              npm run test -- --watch

              # se voce estiver usando o yarn
              yarn run test --watch
              
            

O modo watch está lento, o que fazer?

  • Feche tudo que está aberto e não está sendo usado;
  • Coloque seu notebook na tomada;
  • Habilite o modo de desempenho máximo;
  • Desative animações, transparencia, sombra e tudo que há de bom;

  • Oooooooooooooooou....

Formate sua máquina e instale o linux :D

Como rodar um teste específico?

Como rodar um teste específico sem watch?

              
              npm run test ./src/hello.test.ts
              
            

Como faço para ver o coverage?

              
              npm run test -- --coverage
              
            

Como faço para ver mais detalhes sobre o converage?

              
              # o coverage gera uma pasta chamada coverage
              npm run test -- --coverage

              # dentro da pasta coverage vai existir uns htmls
              cd coverage/lcov-report

              # inicie um servidor estático
              npx serve
              
            

Como faço para ver mais detalhes sobre o converage?

Na CI o jest roda diferente

              
              # forma como o jest é executado na CI
              CI=true npm run test -- --coverage
              
            

Da pra ficar colorido na CI?

              
              # forma como o jest é executado na CI
              CI=true npm run test -- --coverage --color
              
            

Modo verboso

              
              npm run test -- --verbose
              
            

Usando mocks

O que é um mock?

Um mock permite você substituir a implementação original de uma função por um valor conhecido

Ele captura todas as chamadas e parâmetros para usarmos nos expects

Por que precisamos de mocks?

O ideal é que os testes sejam sempre executados com os mesmos valores

Em testes unitários e de integração devemos evitar fazer coisas como requisições HTTPs

              
                // setABTest.ts
                const setABTest = (percentage: number) => {
                  const newTest = Math.floor(Math.random() * 100) + 1;
                  let cookieVal = "";
                
                  if (newTest <= percentage) {
                    cookieVal = "piloto";
                  } else if (newTest <= percentage * 2) {
                    cookieVal = "controle";
                  } else {
                    cookieVal = "fora do teste";
                  }
                
                  return cookieVal;
                };
                
                export default setABTest;
              
            
              
              // setABTest.spec.ts
              import setABTest from './setABTest';

              test('deve retornar piloto', () => {
                expect(setABTest(50)).toBe('piloto')
              })
              
            
              
              // setABTest.spec.ts
              afterEach(() => {
                jest.spyOn(global.Math, "random").mockRestore();
              });
              
              test("deve retornar piloto", () => {
                jest.spyOn(global.Math, "random").mockReturnValue(0.3);
              
                expect(setABTest(40)).toBe("piloto");
              });
              
              test("deve retornar controle", () => {
                jest.spyOn(global.Math, "random").mockReturnValue(0.6);
              
                expect(setABTest(50)).toBe("controle");
              });              
              
            

Ainda não entendi pra que serve um mock

Mais exemplos

              
                // timerGame.ts
                function timerGame(callback) {
                  setTimeout(() => {
                    callback("algum valor aqui");
                  }, 6000);
                }
                
                export default timerGame;
              
            
              
                // timerGame.spec.ts
                import timer from './timerGame';

                jest.useFakeTimers();

                test("executa callback daqui 6 segundos", () => {
                  const mock = jest.fn();

                  timer(mock);

                  jest.advanceTimersByTime(1000);
                  expect(mock).not.toHaveBeenCalled();

                  jest.advanceTimersByTime(5000);
                  expect(mock).toHaveBeenCalledWith('algum valor aqui');
                });
              
            

Como mockar módulos?

              
                // src/utils/useAuth.ts
                const useAuth = () => {
                  // aqui vai bater na rede pra buscar o usuário
                
                  // exemplo de retorno
                  return {
                    isAuthenticated: false,
                    user: null,
                  };
                };
                
                export default useAuth;                
              
            
              
                
              
            

jest.mock

              
                
              
            

jest.doMock e jest.dontMock

              
                
              
            

mock manual

              
                
              
            
              
                
              
            

Princípios da biblioteca Testing Library

Devemos sempre olhar para o DOM e não acessar instâncias dos componentes (como o enzyme faz)

Em geral, devemos testar os componentes da aplicação da maneira como o usuário os usaria.

As implementações de utilitários e APIs devem ser simples e flexíveis.

Ou seja, se está dificil testar é um forte indício de que há algo errado com seu código

O que devo evitar testar?

  • Estado interno de um componente
  • Métodos internos de um componente
  • Métodos de ciclo de vida de um componente
  • Componentes filhos

Exemplos do que não fazer

              
                
              
            
              
                
              
            

Testando componentes React

              
                
              
            
              
                
              
            

Outro exemplo

              
                
              
            
              
                
              
            

Funções úteis

debug

              
                
              
            

PlaygroundURL

              
                
              
            

Outro exemplo

              
                
              
            
              
                
              
            
              
                
              
            

Testando um hook do react

Mais dependências

              
              npm install --save @testing-library/react-hooks
              
            
              
                
              
            
              
                
              
            

Outro exemplo

              
                
              
            
              
                
              
            

Outro exemplo

              
                
              
            
              
                
              
            

Algumas dicas

Descreva os casos de teste da melhor forma possível

Você vai querer saber o que aconteceu caso eles falharem

Evite testar estilo

Isso é responsabilidade dos testes de regressão visual ou de snapshot

Em vez de verificar estilos, verifique:

  • O elemento está com a classe correta?
  • O elemento semântico correto está sendo exibido?
  • Os elementos de acessibilidade estão corretos?

Cuidado com esse tipo de coisa

Isso pode vazar o escopo do seu teste e afetar outros casos de teste

              
                
              
            

Dependendo de onde você colocar isso, vários casos de teste podem quebrar

Resolva os warnings e erros

Se você não fizer isso e seu teste falhar vai ser um inferno encontrar o que causou o erro

Não esqueça de remover consoles e debugs

Code-review

Quem revisa o PR também é responsável pela qualidade do código e dos testes. Então façam code-reviews profundos

Nada de fazer code-review pela interface do Github

Faça PRs pequenos

É impossível garantir qualidade em um PR de 4k de linhas

Façam pair com quem tem mais experiência

Aqui temos várias pessoas da nossa equipe que podem te apoiar

  • Pri Theodoro
  • Aline Bujato
  • Talissa Andrade
  • Henrique Sumitomo
  • Gustavo Fonseca

Não deixe os testes para o final

Afinal você nunca vai ter tempo pra parar e escrever testes. Faça isso parte da sua rotina

Inclua o tempo de teste na estimativa da tarefa

Seu/sua PM não sabe quanto trabalho da pra fazer isso

Não tenha medo de errar

Não deixe que o medo da sua pipeline falhar o impeça de testar

Artigos legais

Erros comuns

O criador da biblioteca React Testing Library criou um excelente artigo falando sobre os erros mais comuns que as pessoas cometem ao usar a lib

Veja detalhes aqui

Testando hooks

Aqui tem um ótimo artigo explicando mais profundamente sobre como testar hooks

Veja detalhes aqui

Um pouco sobre cobertura de código e cobertura de testes

Veja detalhes aqui

Testar detalhes de implementação é uma ideia ruim

Veja detalhes aqui

Agradecimentos

Talissa ❤️

Obrigado pelo suporte com a apresentação e por segurar as pontas no nosso time

George e Neto ❤️

Obrigado pela força com a revisão

Antes de terminar gostaria de deixar uma mensagem

Obrigado