Ir al contenido

Guía de Testing

Estrategias completas de testing con ClaudeKit

Aprende estrategias completas de testing automatizado con ClaudeKit, desde unit tests hasta E2E testing.

¿Por qué testing? Los tests previenen regresiones, documentan comportamiento, y dan confianza para refactorizar.

🧪 Unit Tests

Tests de funciones y componentes aislados.



Frameworks: Vitest, Jest, Mocha

🔗 Integration Tests

Tests de integración entre módulos.



Frameworks: Supertest, Testing Library

🌐 E2E Tests

Tests de flujos completos de usuario.



Frameworks: Playwright, Cypress

⚡ Performance Tests

Tests de carga y rendimiento.



Frameworks: k6, Artillery

graph TD
A[E2E Tests - Pocos] --> B[Integration Tests - Algunos]
B --> C[Unit Tests - Muchos]
style A fill:#ff6b6b
style B fill:#ffd93d
style C fill:#6bcf7f

Regla de oro:

  • 70% Unit Tests - Rápidos, específicos, muchos
  • 20% Integration Tests - Médulos, APIs, databases
  • 10% E2E Tests - Flujos críticos de usuario

Tests de funciones y componentes aislados.

math.test.ts
import { describe, it, expect } from 'vitest';
import { add, multiply } from './math';
describe('Math Functions', () => {
describe('add', () => {
it('should add two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
it('should handle negative numbers', () => {
expect(add(-2, 3)).toBe(1);
});
it('should handle zero', () => {
expect(add(0, 5)).toBe(5);
});
});
describe('multiply', () => {
it('should multiply two numbers', () => {
expect(multiply(2, 3)).toBe(6);
});
it('should multiply by zero', () => {
expect(multiply(5, 0)).toBe(0);
});
});
});

Tests de integración entre módulos, APIs y databases.

api.test.ts
import request from 'supertest';
import { app } from './app';
describe('API Endpoints', () => {
describe('POST /api/users', () => {
it('should create a new user', async () => {
const response = await request(app)
.post('/api/users')
.send({
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
})
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.email).toBe('john@example.com');
});
it('should return 400 for invalid email', async () => {
await request(app)
.post('/api/users')
.send({
name: 'John Doe',
email: 'invalid-email',
password: 'password123'
})
.expect(400);
});
});
describe('GET /api/users/:id', () => {
it('should return user by id', async () => {
const response = await request(app)
.get('/api/users/1')
.expect(200);
expect(response.body).toHaveProperty('name');
expect(response.body).toHaveProperty('email');
});
it('should return 404 for non-existent user', async () => {
await request(app)
.get('/api/users/999')
.expect(404);
});
});
});

Tests de flujos completos de usuario.

login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication Flow', () => {
test('should login successfully', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.fill('input[name="email"]', 'user@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('http://localhost:3000/dashboard');
await expect(page.locator('h1')).toContainText('Welcome');
});
test('should show error for invalid credentials', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.fill('input[name="email"]', 'user@example.com');
await page.fill('input[name="password"]', 'wrongpassword');
await page.click('button[type="submit"]');
await expect(page.locator('.error')).toContainText('Invalid credentials');
});
test('should redirect to login when not authenticated', async ({ page }) => {
await page.goto('http://localhost:3000/dashboard');
await expect(page).toHaveURL('http://localhost:3000/login');
});
});

El comando /test ejecuta el workflow completo de testing:

Ventana de terminal
# Ejecutar todos los tests
claude test
# Tests específicos
claude test "authentication flow"
# Tests con coverage
claude test --coverage
# Tests en paralelo
claude test --parallel
# Tests con reporte
claude test --report
Ventana de terminal
# 1. Implementar feature
claude cook "Add user authentication"
# 2. Tester agent crea tests
claude test "authentication"
# 3. Verificar coverage
claude test --coverage
# 4. Si coverage < 80%, agregar más tests
claude cook "Add tests for edge cases in auth"
# 5. Re-testear
claude test "authentication" --coverage
describe('User Service', () => {
it('should create user with valid data', () => {
// Arrange - Preparar
const userData = {
name: 'John Doe',
email: 'john@example.com'
};
const expectedUser = { id: 1, ...userData };
// Act - Ejecutar
const result = userService.create(userData);
// Assert - Verificar
expect(result).toEqual(expectedUser);
});
});
// ❌ MAL - Tests dependientes
describe('User Service', () => {
let userId: number;
it('creates user', () => {
const user = userService.create({ name: 'John' });
userId = user.id; // Depende del test anterior
});
it('deletes user', () => {
userService.delete(userId); // Usa variable del test anterior
});
});
// ✅ BIEN - Tests independientes
describe('User Service', () => {
it('creates user', () => {
const user = userService.create({ name: 'John' });
expect(user).toHaveProperty('id');
});
it('deletes user', () => {
const user = userService.create({ name: 'John' });
userService.delete(user.id);
expect(userService.find(user.id)).toBeNull();
});
});
// ❌ MAL - Nombre vago
it('works', () => {
expect(add(2, 2)).toBe(4);
});
// ✅ BIEN - Nombre descriptivo
it('should add two positive numbers correctly', () => {
expect(add(2, 2)).toBe(4);
});
// ❌ MAL - Múltiples asserts
it('validates user data', () => {
const user = createUser();
expect(user.name).toBeDefined();
expect(user.email).toBeDefined();
expect(user.age).toBeGreaterThan(0);
});
// ✅ BIEN - Un assert por test
it('should have a name', () => {
const user = createUser();
expect(user.name).toBeDefined();
});
it('should have an email', () => {
const user = createUser();
expect(user.email).toBeDefined();
});
it('should be over 18', () => {
const user = createUser();
expect(user.age).toBeGreaterThan(0);
});
// Mock de API externa
vi.mock('./api', () => ({
fetchUser: vi.fn()
}));
import { fetchUser } from './api';
describe('UserProfile', () => {
it('displays user data', async () => {
const mockUser = { id: 1, name: 'John Doe' };
vi.mocked(fetchUser).mockResolvedValue(mockUser);
// Test code...
});
});
Ventana de terminal
# Ver coverage actual
npm run test:coverage
# Output:
# -----------|---------|----------|---------|---------|-------------------
# File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
# -----------|---------|----------|---------|---------|-------------------
# All files | 85.5 | 78.2 | 92.3 | 86.1 |
# utils.ts | 95.2 | 90.5 | 100 | 95.2 | 45
# api.ts | 72.1 | 65.3 | 85.7 | 73.4 | 123-145
# -----------|---------|----------|---------|---------|-------------------
Tipo de ProyectoStatementsBranchesFunctionsLines
CRÍTICO (Financiero, Salud)> 95%> 90%> 95%> 95%
ALTO (Core business)> 85%> 80%> 85%> 85%
MEDIO (General)> 75%> 70%> 75%> 75%
BAJO (Prototipos)> 60%> 50%> 60%> 60%
name: Test Suite
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Generate coverage
run: npm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
- name: Comment PR with coverage
uses: romeovs/lcov-reporter-action@v0.3.1
with:
lcov-file: ./coverage/lcov.info
github-token: ${{ secrets.GITHUB_TOKEN }}

Problema: Tests que pasan a veces y fallan otras.

Soluciones:

  1. Aislar el test
it('should work independently', async () => {
// Reset state before test
await resetDatabase();
// Test code...
});
  1. Agregar waits explícitos
it('should wait for async operation', async () => {
await waitFor(() => {
expect(element).toBeVisible();
});
});
  1. Evitar tiempos fijos
// ❌ MAL
await sleep(1000);
// ✅ BIEN
await waitFor(() => expect(result).toBeDefined());

Problema: Suite de tests toma demasiado tiempo.

Soluciones:

  1. Ejecutar tests en paralelo
Ventana de terminal
vitest --parallel
  1. Usar mocks para operaciones lentas
vi.mock('./database', () => ({
query: vi.fn().mockResolvedValue([])
}));
  1. Tests de integración selectivos
// Solo correr tests lentos en CI
const runSlowTests = process.env.CI === 'true';
describe('API Integration', () => {
(runSlowTests ? describe : describe.skip)('Slow endpoints', () => {
// Tests lentos...
});
});

Explora las skills más utilizadas en producción.


¿Preguntas? Usa claude ask "tu pregunta sobre testing" en cualquier momento.