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:
- Testes Unitários: Base da pirâmide, maior quantidade, testam funções e componentes isoladamente
- Testes de Integração: Meio da pirâmide, testam a interação entre componentes
- Testes End-to-End: Topo da pirâmide, menor quantidade, testam o fluxo completo da aplicação
Tipos de Testes
Testes Unitários
Testam unidades individuais de código, como funções, classes ou componentes isolados:
export function validateEmail(email) { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email);}
export function validatePassword(password) { return password.length >= 8;}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:
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 testesasync 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:
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çãovi.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:
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:
# Instalar Vitestnpm install -D vitest jsdom @testing-library/domConfiguraçã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:
# Instalar Playwrightnpm init playwright@latestConfiguraçã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:
# Instalar ESLintnpm install -D eslint eslint-plugin-astro @typescript-eslint/parserConfiguraçã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:
# Instalar Prettiernpm install -D prettier prettier-plugin-astroConfiguraçã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:
# Instalar TypeScriptnpm install -D typescriptConfiguraçã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:
# Executar testes com coberturanpm run test:coverageExemplo 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:
# Instalar Lighthouse CInpm install -D @lhci/cliConfiguraçã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
- Clareza: O código é fácil de entender?
- Manutenibilidade: O código será fácil de manter no futuro?
- Performance: O código é eficiente?
- Segurança: O código tem vulnerabilidades?
- Testabilidade: O código é testável?
- 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 lighthouseTestes de Regressão
Implemente testes de regressão para garantir que novas alterações não quebrem funcionalidades existentes:
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:
- Acessibilidade: Práticas para tornar seu site acessível
- SEO e Performance: Otimização para mecanismos de busca e performance
- Implantação e DevOps: Estratégias de implantação e operações
Última atualização: 21 de março de 2025