Pular para o conteúdo

11. Testes e Qualidade

Este documento detalha estratégias e práticas para garantir a qualidade de soluções web desenvolvidas com Astro 5, incluindo testes automatizados, análise estática de código e processos de garantia de qualidade.

Estratégias de Teste

Pirâmide de Testes

A pirâmide de testes é um modelo que sugere a distribuição ideal de diferentes tipos de testes:

  1. Testes Unitários: Base da pirâmide, maior quantidade, testam funções e componentes isoladamente
  2. Testes de Integração: Meio da pirâmide, testam a interação entre componentes
  3. Testes End-to-End: Topo da pirâmide, menor quantidade, testam o fluxo completo da aplicação

Pirâmide de Testes

Tipos de Testes

Testes Unitários

Testam unidades individuais de código, como funções, classes ou componentes isolados:

src/utils/validation.js
export function validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
export function validatePassword(password) {
return password.length >= 8;
}
src/utils/validation.test.js
import { describe, it, expect } from 'vitest';
import { validateEmail, validatePassword } from './validation';
describe('Funções de validação', () => {
describe('validateEmail', () => {
it('deve retornar true para emails válidos', () => {
expect(validateEmail('usuario@exemplo.com')).toBe(true);
expect(validateEmail('nome.sobrenome@empresa.com.br')).toBe(true);
});
it('deve retornar false para emails inválidos', () => {
expect(validateEmail('usuario@')).toBe(false);
expect(validateEmail('usuario@exemplo')).toBe(false);
expect(validateEmail('usuario.exemplo.com')).toBe(false);
expect(validateEmail('')).toBe(false);
});
});
describe('validatePassword', () => {
it('deve retornar true para senhas com 8 ou mais caracteres', () => {
expect(validatePassword('senha123')).toBe(true);
expect(validatePassword('senhamuito1onga')).toBe(true);
});
it('deve retornar false para senhas com menos de 8 caracteres', () => {
expect(validatePassword('senha1')).toBe(false);
expect(validatePassword('')).toBe(false);
});
});
});

Testes de Componentes

Testam componentes isoladamente, verificando sua renderização e comportamento:

src/components/ui/Button.test.js
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/dom';
import Button from './Button.astro';
// Função auxiliar para renderizar componentes Astro em testes
async function renderAstroComponent(Component, props = {}) {
const html = await Component.render(props);
const container = document.createElement('div');
container.innerHTML = html;
return container;
}
describe('Button Component', () => {
it('deve renderizar com o texto correto', async () => {
const container = await renderAstroComponent(Button, {
text: 'Clique aqui'
});
expect(container.textContent).toContain('Clique aqui');
});
it('deve aplicar a classe correta com base na variante', async () => {
const container = await renderAstroComponent(Button, {
text: 'Botão Primário',
variant: 'primary'
});
const button = container.querySelector('button');
expect(button.classList.contains('btn-primary')).toBe(true);
});
it('deve ser desabilitado quando a prop disabled é true', async () => {
const container = await renderAstroComponent(Button, {
text: 'Botão Desabilitado',
disabled: true
});
const button = container.querySelector('button');
expect(button.disabled).toBe(true);
});
});

Testes de Integração

Testam a interação entre diferentes partes da aplicação:

src/features/auth/login.test.js
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/dom';
import { setupLoginForm } from './login';
import { authService } from '../../services/auth';
// Mock do serviço de autenticação
vi.mock('../../services/auth', () => ({
authService: {
login: vi.fn()
}
}));
describe('Login Feature', () => {
beforeEach(() => {
// Configurar o DOM para o teste
document.body.innerHTML = `
<form id="login-form">
<input type="email" id="email" name="email" />
<input type="password" id="password" name="password" />
<div id="error-message" class="hidden"></div>
<button type="submit">Entrar</button>
</form>
`;
// Inicializar o formulário
setupLoginForm();
});
afterEach(() => {
vi.resetAllMocks();
});
it('deve chamar o serviço de login com as credenciais corretas', async () => {
// Configurar o mock para retornar sucesso
authService.login.mockResolvedValue({ success: true });
// Preencher o formulário
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const form = document.getElementById('login-form');
fireEvent.input(emailInput, { target: { value: 'usuario@exemplo.com' } });
fireEvent.input(passwordInput, { target: { value: 'senha123' } });
fireEvent.submit(form);
// Verificar se o serviço foi chamado com os parâmetros corretos
await waitFor(() => {
expect(authService.login).toHaveBeenCalledWith({
email: 'usuario@exemplo.com',
password: 'senha123'
});
});
});
it('deve exibir mensagem de erro quando o login falha', async () => {
// Configurar o mock para retornar erro
authService.login.mockRejectedValue(new Error('Credenciais inválidas'));
// Preencher o formulário
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const form = document.getElementById('login-form');
fireEvent.input(emailInput, { target: { value: 'usuario@exemplo.com' } });
fireEvent.input(passwordInput, { target: { value: 'senhaerrada' } });
fireEvent.submit(form);
// Verificar se a mensagem de erro é exibida
const errorMessage = document.getElementById('error-message');
await waitFor(() => {
expect(errorMessage.classList.contains('hidden')).toBe(false);
expect(errorMessage.textContent).toContain('Credenciais inválidas');
});
});
});

Testes End-to-End

Testam o fluxo completo da aplicação, simulando a interação do usuário:

tests/e2e/login.spec.js
import { test, expect } from '@playwright/test';
test.describe('Login Page', () => {
test('deve fazer login com credenciais válidas', async ({ page }) => {
// Navegar para a página de login
await page.goto('/login');
// Preencher o formulário
await page.fill('#email', 'usuario@exemplo.com');
await page.fill('#password', 'senha123');
// Clicar no botão de login
await page.click('button[type="submit"]');
// Verificar se foi redirecionado para o dashboard
await expect(page).toHaveURL('/dashboard');
// Verificar se elementos do dashboard estão visíveis
await expect(page.locator('h1')).toContainText('Dashboard');
await expect(page.locator('.user-info')).toBeVisible();
});
test('deve exibir erro com credenciais inválidas', async ({ page }) => {
// Navegar para a página de login
await page.goto('/login');
// Preencher o formulário com credenciais inválidas
await page.fill('#email', 'usuario@exemplo.com');
await page.fill('#password', 'senhaerrada');
// Clicar no botão de login
await page.click('button[type="submit"]');
// Verificar se permanece na página de login
await expect(page).toHaveURL('/login');
// Verificar se a mensagem de erro é exibida
await expect(page.locator('.error-message')).toBeVisible();
await expect(page.locator('.error-message')).toContainText('Credenciais inválidas');
});
});

Ferramentas de Teste

Vitest

Vitest é um framework de testes rápido e leve, compatível com a API do Jest:

Terminal window
# Instalar Vitest
npm install -D vitest jsdom @testing-library/dom

Configuração no vitest.config.js:

import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./vitest.setup.js'],
include: ['src/**/*.test.{js,ts}'],
coverage: {
reporter: ['text', 'json', 'html'],
exclude: ['node_modules/', 'src/**/*.d.ts'],
},
},
});

Scripts no package.json:

{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}
}

Playwright

Playwright é uma ferramenta para testes end-to-end que suporta múltiplos navegadores:

Terminal window
# Instalar Playwright
npm init playwright@latest

Configuração no playwright.config.js:

import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html', { open: 'never' }]],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 12'] },
},
],
webServer: {
command: 'npm run preview',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});

Scripts no package.json:

{
"scripts": {
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
}
}

Análise Estática de Código

ESLint

ESLint é uma ferramenta para identificar e corrigir problemas no código JavaScript:

Terminal window
# Instalar ESLint
npm install -D eslint eslint-plugin-astro @typescript-eslint/parser

Configuração no .eslintrc.js:

module.exports = {
extends: [
'eslint:recommended',
'plugin:astro/recommended',
],
overrides: [
{
files: ['*.astro'],
parser: 'astro-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
extraFileExtensions: ['.astro'],
},
rules: {
// Regras específicas para arquivos Astro
},
},
{
files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
rules: {
'no-unused-vars': 'error',
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-alert': 'error',
'no-debugger': 'error',
'prefer-const': 'warn',
'arrow-body-style': ['warn', 'as-needed'],
'curly': ['warn', 'multi-line'],
},
},
],
};

Scripts no package.json:

{
"scripts": {
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.astro",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx,.astro --fix"
}
}

Prettier

Prettier é um formatador de código que garante consistência no estilo:

Terminal window
# Instalar Prettier
npm install -D prettier prettier-plugin-astro

Configuração no .prettierrc:

{
"printWidth": 100,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"plugins": ["prettier-plugin-astro"],
"overrides": [
{
"files": "*.astro",
"options": {
"parser": "astro"
}
}
]
}

Scripts no package.json:

{
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}

TypeScript

TypeScript adiciona tipagem estática ao JavaScript, ajudando a prevenir erros:

Terminal window
# Instalar TypeScript
npm install -D typescript

Configuração no tsconfig.json:

{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"strictNullChecks": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src/**/*.ts", "src/**/*.astro"],
"exclude": ["node_modules", "dist"]
}

Script no package.json:

{
"scripts": {
"typecheck": "tsc --noEmit"
}
}

Métricas de Qualidade

Cobertura de Testes

A cobertura de testes mede quanto do código está sendo testado:

Terminal window
# Executar testes com cobertura
npm run test:coverage

Exemplo de relatório de cobertura:

--------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files | 85.71 | 76.92 | 83.33 | 85.71 |
src/utils | 85.71 | 76.92 | 83.33 | 85.71 |
validation.js | 85.71 | 76.92 | 83.33 | 85.71 | 15-18
--------------------|---------|----------|---------|---------|-------------------

Lighthouse

Lighthouse avalia a performance, acessibilidade, SEO e boas práticas do site:

Terminal window
# Instalar Lighthouse CI
npm install -D @lhci/cli

Configuração no lighthouserc.js:

module.exports = {
ci: {
collect: {
url: ['http://localhost:3000/'],
startServerCommand: 'npm run preview',
numberOfRuns: 3,
},
upload: {
target: 'temporary-public-storage',
},
assert: {
preset: 'lighthouse:recommended',
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
},
},
},
};

Script no package.json:

{
"scripts": {
"lighthouse": "lhci autorun"
}
}

Processos de Garantia de Qualidade

Revisão de Código

Estabeleça um processo de revisão de código para garantir qualidade e compartilhar conhecimento:

Diretrizes para Revisão de Código

  1. Clareza: O código é fácil de entender?
  2. Manutenibilidade: O código será fácil de manter no futuro?
  3. Performance: O código é eficiente?
  4. Segurança: O código tem vulnerabilidades?
  5. Testabilidade: O código é testável?
  6. Padrões: O código segue os padrões do projeto?

Checklist de Revisão

  • O código segue os padrões de estilo do projeto
  • O código inclui testes adequados
  • A documentação foi atualizada
  • Não há código duplicado
  • Não há problemas de segurança óbvios
  • O código é eficiente e performático
  • As mensagens de erro são claras e úteis
  • O código é acessível

Integração Contínua

Configure um pipeline de CI para executar testes e verificações automaticamente:

GitHub Actions

Exemplo de configuração no .github/workflows/ci.yml:

name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npm run typecheck
- name: Format check
run: npm run format:check
- name: Run tests
run: npm run test
- name: Run E2E tests
run: npm run test:e2e
- name: Build
run: npm run build
- name: Lighthouse
run: npm run lighthouse

Testes de Regressão

Implemente testes de regressão para garantir que novas alterações não quebrem funcionalidades existentes:

tests/regression/visual.spec.js
import { test, expect } from '@playwright/test';
test.describe('Testes de Regressão Visual', () => {
const pages = [
{ name: 'home', path: '/' },
{ name: 'about', path: '/about' },
{ name: 'contact', path: '/contact' },
];
for (const { name, path } of pages) {
test(`página ${name} deve corresponder ao snapshot`, async ({ page }) => {
await page.goto(path);
// Aguarda carregamento completo
await page.waitForLoadState('networkidle');
// Compara com snapshot
await expect(page).toHaveScreenshot(`${name}.png`, {
fullPage: true,
maxDiffPixelRatio: 0.01,
});
});
}
});

Conclusão

A implementação de uma estratégia abrangente de testes e qualidade é essencial para o desenvolvimento de soluções web robustas e confiáveis com Astro 5. Ao combinar testes automatizados, análise estática de código e processos de garantia de qualidade, você pode identificar e corrigir problemas precocemente, reduzir o custo de manutenção e melhorar a experiência do usuário.

Lembre-se de adaptar estas práticas às necessidades específicas do seu projeto, considerando fatores como tamanho da equipe, complexidade da aplicação e requisitos de negócio.

Os próximos documentos detalharão outros aspectos do desenvolvimento:


Última atualização: 21 de março de 2025