0%
Guia completo de testes em JavaScript

Guia completo de testes em JavaScript

Estratégias e ferramentas para testar suas aplicações JavaScript de forma eficaz e garantir a qualidade delas.

I

InSkillCoach

· min

Guia completo de testes em JavaScript

Os testes são um aspecto fundamental do desenvolvimento de software profissional. Eles garantem que seu código funcione como esperado, facilitam a manutenção e permitem detectar regressões. Este artigo explora as diferentes abordagens e ferramentas para testar suas aplicações JavaScript de forma eficaz.

Por que testar seu código?

  • Confiança: Garantia de que seu código funciona corretamente
  • Documentação: Os testes servem como documentação viva
  • Refatoração segura: Modificação do código sem medo de quebrar funcionalidades
  • Qualidade: Redução de bugs e melhoria do design
  • Colaboração: Facilitação do trabalho em equipe
  • Entrega contínua: Automatização das verificações antes do deploy

Tipos de testes

Testes unitários

Os testes unitários verificam o funcionamento das menores unidades de código (funções, métodos, classes) de forma isolada.

// Função a ser testada
function somme(a, b) {
  return a + b;
}

// Teste unitário com Jest
test('additionne correctement deux nombres', () => {
  expect(somme(2, 3)).toBe(5);
  expect(somme(-1, 1)).toBe(0);
  expect(somme(0, 0)).toBe(0);
});

Testes de integração

Os testes de integração verificam se diferentes partes do sistema funcionam corretamente em conjunto.

// Teste de integração com Jest
describe('Service utilisateur', () => {
  test('crée un utilisateur et le récupère de la base de données', async () => {
    // Criar um usuário
    const userId = await userService.createUser({
      name: 'Alice',
      email: 'alice@example.com'
    });
    
    // Recuperar o usuário
    const user = await userService.getUserById(userId);
    
    // Verificar se o usuário foi recuperado corretamente
    expect(user).toEqual({
      id: userId,
      name: 'Alice',
      email: 'alice@example.com'
    });
  });
});

Testes funcionais / End-to-End (E2E)

Os testes E2E verificam o comportamento da aplicação do ponto de vista do usuário final.

// Teste E2E com Cypress
describe('Formulaire de connexion', () => {
  it('permet à un utilisateur de se connecter', () => {
    cy.visit('/login');
    
    cy.get('input[name="email"]').type('utilisateur@exemple.fr');
    cy.get('input[name="password"]').type('motdepasse123');
    cy.get('button[type="submit"]').click();
    
    // Verificar se o usuário é redirecionado para o painel
    cy.url().should('include', '/dashboard');
    cy.contains('Bienvenue, Utilisateur').should('be.visible');
  });
});

Testes de componentes

Para aplicações baseadas em componentes (React, Vue, Angular), os testes de componentes verificam a renderização e o comportamento dos componentes de UI.

// Teste de componente React com React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
import Compteur from './Compteur';

test('incrémente le compteur lorsque le bouton est cliqué', () => {
  render(<Compteur />);
  
  // Verificar o estado inicial
  expect(screen.getByText('Compteur: 0')).toBeInTheDocument();
  
  // Clicar no botão
  fireEvent.click(screen.getByText('Incrémenter'));
  
  // Verificar se o contador foi incrementado
  expect(screen.getByText('Compteur: 1')).toBeInTheDocument();
});

Testes de desempenho

Os testes de desempenho verificam se sua aplicação atende aos requisitos de velocidade e eficiência.

// Teste de desempenho com Lighthouse CI
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/'],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'first-contentful-paint': ['warn', { maxNumericValue: 2000 }],
        'interactive': ['error', { maxNumericValue: 3000 }],
        'max-potential-fid': ['error', { maxNumericValue: 100 }],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

Testes de segurança

Os testes de segurança identificam vulnerabilidades potenciais em sua aplicação.

// Exemplo com OWASP ZAP (via CLI)
// zap-cli quick-scan --self-contained --start-options "-config api.disablekey=true" https://example.com

Frameworks e ferramentas de teste populares

Jest

Jest é um framework de teste JavaScript completo, com configuração mínima.

// Instalação
// npm install --save-dev jest

// Configuração mínima (package.json)
{
  "scripts": {
    "test": "jest"
  }
}

// Exemplo de teste
const { somme } = require('./math');

describe('Fonctions mathématiques', () => {
  test('somme additionne deux nombres', () => {
    expect(somme(1, 2)).toBe(3);
  });
  
  test('somme gère les nombres négatifs', () => {
    expect(somme(-1, -2)).toBe(-3);
  });
  
  test('somme avec zéro retourne l\'autre nombre', () => {
    expect(somme(0, 5)).toBe(5);
  });
});

Mocha + Chai

Mocha é um framework de teste flexível, frequentemente usado com Chai para as asserções.

// Instalação
// npm install --save-dev mocha chai

// Exemplo de teste
const { expect } = require('chai');
const { somme } = require('./math');

describe('Fonctions mathématiques', () => {
  it('somme additionne deux nombres', () => {
    expect(somme(1, 2)).to.equal(3);
  });
  
  it('somme gère les nombres négatifs', () => {
    expect(somme(-1, -2)).to.equal(-3);
  });
});

Cypress

Cypress é uma ferramenta moderna de teste E2E que é executada no navegador.

// Instalação
// npm install --save-dev cypress

// Exemplo de teste
describe('Page d\'accueil', () => {
  beforeEach(() => {
    cy.visit('/');
  });
  
  it('affiche le titre correct', () => {
    cy.get('h1').should('contain', 'Bienvenue sur notre site');
  });
  
  it('navigue vers la page À propos', () => {
    cy.get('nav').contains('À propos').click();
    cy.url().should('include', '/about');
    cy.get('h1').should('contain', 'À propos de nous');
  });
});

Playwright

Playwright é uma ferramenta de teste E2E que oferece suporte a vários navegadores.

// Instalação
// npm install --save-dev @playwright/test

// Exemplo de teste
const { test, expect } = require('@playwright/test');

test('page d\'accueil a le bon titre', async ({ page }) => {
  await page.goto('https://example.com');
  
  // Verificar o título
  const title = await page.title();
  expect(title).toBe('Example Domain');
  
  // Verificar o conteúdo
  const heading = await page.textContent('h1');
  expect(heading).toBe('Example Domain');
});

Testing Library

Testing Library é uma família de bibliotecas para testar interfaces de usuário com foco no usuário.

// Instalação para React
// npm install --save-dev @testing-library/react @testing-library/jest-dom

// Exemplo de teste
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Formulaire from './Formulaire';

test('soumet le formulaire avec les valeurs correctes', () => {
  const handleSubmit = jest.fn();
  render(<Formulaire onSubmit={handleSubmit} />);
  
  // Preencher o formulário
  fireEvent.change(screen.getByLabelText(/nom/i), {
    target: { value: 'Alice' },
  });
  
  fireEvent.change(screen.getByLabelText(/email/i), {
    target: { value: 'alice@example.com' },
  });
  
  // Enviar o formulário
  fireEvent.click(screen.getByRole('button', { name: /soumettre/i }));
  
  // Verificar se a função onSubmit foi chamada com os valores corretos
  expect(handleSubmit).toHaveBeenCalledWith({
    nom: 'Alice',
    email: 'alice@example.com',
  });
});

Storybook

Storybook permite desenvolver e testar componentes de UI de forma isolada.

// Instalação
// npx sb init

// Exemplo de story
// Button.stories.js
import React from 'react';
import { Button } from './Button';

export default {
  title: 'Components/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

Abordagens de teste

Test-Driven Development (TDD)

O TDD segue um ciclo em três etapas:

  1. Red: Escrever um teste que falha
  2. Green: Escrever o código mínimo para fazer o teste passar
  3. Refactor: Melhorar o código sem alterar seu comportamento
// Exemplo de TDD
// 1. Red: Escrever um teste que falha
test('convertit les degrés Celsius en Fahrenheit', () => {
  expect(celsiusToFahrenheit(0)).toBe(32);
  expect(celsiusToFahrenheit(100)).toBe(212);
});

// 2. Green: Escrever o código mínimo para fazer o teste passar
function celsiusToFahrenheit(celsius) {
  return celsius * 9/5 + 32;
}

// 3. Refactor: Melhorar o código (se necessário)
function celsiusToFahrenheit(celsius) {
  const factor = 9/5;
  const freezingPointDiff = 32;
  return celsius * factor + freezingPointDiff;
}

Behavior-Driven Development (BDD)

O BDD se concentra no comportamento esperado do sistema do ponto de vista do usuário.

// Exemplo com Cucumber.js
// Feature: Connexion utilisateur
//   En tant qu'utilisateur enregistré
//   Je veux pouvoir me connecter
//   Afin d'accéder à mon compte

// Scénario: Connexion réussie
//   Étant donné que je suis sur la page de connexion
//   Quand je saisis mon email "utilisateur@exemple.fr"
//   Et je saisis mon mot de passe "motdepasse123"
//   Et je clique sur le bouton "Se connecter"
//   Alors je devrais être redirigé vers la page "Tableau de bord"
//   Et je devrais voir "Bienvenue, Utilisateur"

// Implementação dos passos
const { Given, When, Then } = require('@cucumber/cucumber');

Given('je suis sur la page de connexion', async function() {
  await this.navigateTo('/login');
});

When('je saisis mon email {string}', async function(email) {
  await this.fillField('email', email);
});

// ... outros passos

Mocks, Stubs e Spies

Mocks

Os mocks substituem dependências reais por versões simuladas.

// Exemplo com Jest
test('envoie un email de bienvenue lors de l\'inscription', () => {
  // Mock do serviço de e-mail
  const emailService = {
    sendWelcomeEmail: jest.fn()
  };
  
  // Sistema sob teste
  const userService = new UserService(emailService);
  
  // Ação
  userService.registerUser('alice@example.com', 'password123');
  
  // Asserção
  expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith('alice@example.com');
});

Stubs

Os stubs fornecem respostas predefinidas às chamadas de função.

// Exemplo com Sinon.js
const sinon = require('sinon');
const database = require('./database');

test('récupère les données utilisateur', async () => {
  // Criar um stub para o método query
  const queryStub = sinon.stub(database, 'query');
  
  // Configurar o stub para retornar dados específicos
  queryStub.withArgs('SELECT * FROM users WHERE id = ?', [42])
    .resolves([{ id: 42, name: 'Alice' }]);
  
  // Sistema sob teste
  const userService = new UserService(database);
  
  // Ação
  const user = await userService.getUserById(42);
  
  // Asserção
  expect(user).toEqual({ id: 42, name: 'Alice' });
  
  // Restaurar o stub
  queryStub.restore();
});

Spies

Os spies observam as chamadas de função sem modificar o comportamento delas.

// Exemplo com Jest
test('enregistre les erreurs dans la console', () => {
  // Criar um spy em console.error
  const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
  
  // Ação que deveria gerar um erro
  const calculator = new Calculator();
  calculator.divide(10, 0);
  
  // Asserção
  expect(errorSpy).toHaveBeenCalledWith('Division par zéro');
  
  // Restaurar o spy
  errorSpy.mockRestore();
});

Testes assíncronos

Promises

// Teste com Jest
test('récupère les données utilisateur', () => {
  return userService.getUserById(42)
    .then(user => {
      expect(user.name).toBe('Alice');
    });
});

// Ou com async/await
test('récupère les données utilisateur', async () => {
  const user = await userService.getUserById(42);
  expect(user.name).toBe('Alice');
});

Callbacks

// Teste com Jest
test('lit un fichier', done => {
  fs.readFile('data.txt', 'utf8', (err, data) => {
    if (err) {
      done(err);
      return;
    }
    
    try {
      expect(data).toContain('Hello, world');
      done();
    } catch (error) {
      done(error);
    }
  });
});

Cobertura de código

A cobertura de código mede qual parte do seu código é executada durante os testes.

// Configuração do Jest para cobertura
// package.json
{
  "jest": {
    "collectCoverage": true,
    "collectCoverageFrom": [
      "src/**/*.{js,jsx}",
      "!**/node_modules/**",
      "!**/vendor/**"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

Integração contínua (CI)

A integração contínua executa automaticamente seus testes a cada alteração do código.

# Exemplo com GitHub Actions
# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '16'
        
    - name: Install dependencies
      run: npm ci
      
    - name: Run tests
      run: npm test
      
    - name: Upload coverage
      uses: codecov/codecov-action@v2

Boas práticas

  1. Teste primeiro as funcionalidades críticas: Concentre-se no código mais importante
  2. Mantenha os testes simples e legíveis: Um teste deve ser fácil de entender
  3. Um teste, uma asserção: Cada teste deve verificar uma única coisa
  4. Isole seus testes: Os testes não devem depender uns dos outros
  5. Use dados de teste realistas: Evite dados simplistas demais
  6. Evite testes frágeis: Os testes não devem quebrar por motivos superficiais
  7. Execute os testes regularmente: Idealmente a cada alteração do código
  8. Mantenha seus testes: Testes são código e exigem manutenção

Armadilhas comuns a evitar

  1. Testar a implementação em vez do comportamento: Concentre-se no que o código faz, não em como ele faz
  2. Ignorar os casos-limite: Teste os valores extremos e os casos de erro
  3. Testar apenas o caminho feliz: Não se esqueça de testar os casos de falha
  4. Testes muito acoplados ao código: Testes específicos demais quebram facilmente durante refatorações
  5. Negligenciar os testes de regressão: Adicione testes para os bugs corrigidos
  6. Testes lentos: Testes lentos desencorajam a execução frequente

Conclusão

Os testes são um investimento que gera dividendos ao longo de toda a vida de um projeto. Eles melhoram a qualidade do código, facilitam a manutenção e permitem deploys mais frequentes e mais confiáveis.

Não existe uma abordagem universal para testes. A estratégia ideal depende da natureza do seu projeto, da sua equipe e das suas restrições. O importante é começar por algum ponto e melhorar progressivamente a sua cobertura de testes.

Ao adotar uma cultura de testes na sua equipe e seguir as boas práticas apresentadas neste artigo, você pode melhorar consideravelmente a qualidade e a manutenibilidade das suas aplicações JavaScript.

InSkillCoach

Sobre InSkillCoach

Especialista em treinamento e tecnologias

Coach especializado em tecnologias avançadas e IA, apoiado pela GNeurone Inc.

Certificações:

  • AWS Certified Solutions Architect – Professional
  • Certificações Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
1.6k
102

Comentários

Os comentários são alimentados pelo GitHub Discussions

Conecte-se com o GitHub para participar da discussão

Link copiado!