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:
- Red: Escrever um teste que falha
- Green: Escrever o código mínimo para fazer o teste passar
- 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
- Teste primeiro as funcionalidades críticas: Concentre-se no código mais importante
- Mantenha os testes simples e legíveis: Um teste deve ser fácil de entender
- Um teste, uma asserção: Cada teste deve verificar uma única coisa
- Isole seus testes: Os testes não devem depender uns dos outros
- Use dados de teste realistas: Evite dados simplistas demais
- Evite testes frágeis: Os testes não devem quebrar por motivos superficiais
- Execute os testes regularmente: Idealmente a cada alteração do código
- Mantenha seus testes: Testes são código e exigem manutenção
Armadilhas comuns a evitar
- Testar a implementação em vez do comportamento: Concentre-se no que o código faz, não em como ele faz
- Ignorar os casos-limite: Teste os valores extremos e os casos de erro
- Testar apenas o caminho feliz: Não se esqueça de testar os casos de falha
- Testes muito acoplados ao código: Testes específicos demais quebram facilmente durante refatorações
- Negligenciar os testes de regressão: Adicione testes para os bugs corrigidos
- 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.
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+
Comentários
Os comentários são alimentados pelo GitHub Discussions
Conecte-se com o GitHub para participar da discussão