Generación de Tests con IA

Crea tests unitarios completos en segundos con inteligencia artificial

Por Qué Generar Tests con IA

Escribir tests es tedioso pero esencial. La IA puede generar suites completas de tests en segundos, incluyendo casos normales, edge cases y manejo de errores. Esto te ahorra horas de trabajo y mejora la cobertura de tu código.

Tipos de Tests que la IA Puede Generar

1. Tests unitarios

"Genera tests unitarios con Jest para esta función: [código] Incluye: - Casos normales (happy path) - Edge cases (empty array, null, undefined) - Casos de error (tipos incorrectos) - Mock de dependencias externas"

2. Tests de integración

"Crea tests de integración para este endpoint Express: [código] Usa supertest. Mockea la base de datos con jest.mock(). Prueba: GET /users, POST /users, PUT /users/:id, DELETE /users/:id"

3. Tests E2E

"Escribe tests E2E con Playwright para este flujo: 1. Usuario va a /login 2. Introduce email y password 3. Click en 'Iniciar sesión' 4. Verifica que redirige a /dashboard 5. Verifica que muestra el nombre del usuario"

Prompt Maestro para Tests

"Genera tests completos para esta función [lenguaje/framework]: [código] Requisitos: - Framework: [Jest/Vitest/Pytest/JUnit] - Cobertura objetivo: 100% - Incluye tests para: * Casos normales con datos válidos * Edge cases: null, undefined, [], {}, 0, "" * Tipos de datos incorrectos * Límites (máximo, mínimo) * Errores esperados y su manejo - Usa describe/it con nombres descriptivos - Mockea: [lista de dependencias] - Incluye assertions específicas (no solo toBeTruthy)"

Ejemplo: Antes y Después

Función a testear

function calculateDiscount(price, userType, isVip) { if (price <= 0) throw new Error('Price must be positive'); if (!userType) throw new Error('User type required'); let discount = 0; if (userType === 'premium') discount = 0.1; if (isVip) discount += 0.05; if (price > 1000) discount += 0.02; return price * (1 - discount); }

Tests generados por IA

describe('calculateDiscount', () => { describe('valid inputs', () => { it('returns full price for regular user', () => { expect(calculateDiscount(100, 'regular', false)).toBe(100); }); it('applies 10% discount for premium', () => { expect(calculateDiscount(100, 'premium', false)).toBe(90); }); it('adds 5% VIP discount', () => { expect(calculateDiscount(100, 'premium', true)).toBe(85); }); it('adds 2% for orders over 1000', () => { expect(calculateDiscount(1000, 'premium', false)).toBe(880); }); }); describe('edge cases', () => { it('throws for price <= 0', () => { expect(() => calculateDiscount(0, 'regular', false)) .toThrow('Price must be positive'); }); it('throws for missing userType', () => { expect(() => calculateDiscount(100, null, false)) .toThrow('User type required'); }); }); });

Tips para Mejores Tests con IA

Cobertura de Tests y Qué Priorizar

La cobertura de tests mide qué porcentaje de tu código se ejecuta durante la suite de tests. Aunque el 100% de cobertura es un objetivo loable, no siempre es práctico ni necesario. Lo importante es testear lo que realmente importa: la lógica de negocio, los edge cases críticos y las integraciones entre sistemas.

La Pirámide de Testing

La pirámide de testing es un modelo que describe la proporción ideal de cada tipo de test en tu suite. La base de la pirámide son los tests unitarios (muchos, rápidos, baratos), el medio son los tests de integración (algunos, moderados) y la punta son los tests E2E (pocos, lentos, caros pero más realistas).

Pirámide de Testing para una app típica: / E2E \ ~10% - 5-10 tests / Integr. \ ~20% - 20-50 tests / Unit \ ~70% - 200-500 tests /_____________\ Tests Unitarios (base): - Rápidos (milisegundos) - Aislados (mocks de todo) - Testean funciones individuales - Ejemplo: "calculateDiscount(100, 'premium') === 90" Tests de Integración (medio): - Moderados (segundos) - Testean interacción entre módulos - Usan bases de datos de test - Ejemplo: "POST /users crea usuario en DB y envía email" Tests E2E (punta): - Lentos (minutos) - Simulan usuario real - Testean flujos completos - Ejemplo: "Registro → Login → Compra → Confirmación"

Qué testear primero

Cuando tienes un codebase sin tests y necesitas empezar a añadir cobertura, prioriza en este orden para maximizar el impacto de tu esfuerzo de testing.

Prioridades de testing (de mayor a menor impacto): 1. Lógica de negocio crítica - Cálculos de precios, pagos, descuentos - Reglas de permisos y autenticación - Validaciones de datos de usuario 2. Funciones que han tenido bugs - Cada bug reportado debería tener un test que lo prevenga - "Regression test": el test falla sin el fix, pasa con el fix 3. Edge cases y manejo de errores - Inputs vacíos, null, undefined - Límites numéricos (0, MAX_INT, negativos) - Estados inesperados de la red o base de datos 4. Integraciones con servicios externos - APIs de pago (Stripe, PayPal) - Servicios de email (SendGrid, SES) - APIs de terceros (Google Maps, etc.) Prompt para la IA: "Analiza este módulo y genera una lista priorizada de tests necesarios. Para cada test, indica: tipo (unit/int/e2e), qué scenario cubre, y qué bug potencial previene. [código del módulo]"

Mocks, Stubs y Spies

Los dobles de test son objetos que simulan el comportamiento de dependencias reales. Entender cuándo usar cada tipo es fundamental para escribir tests que sean fiables, rápidos y mantenibles. La IA puede generar estos dobles automáticamente si le indicas qué tipo necesitas.

Stubs: respuestas predefinidas

Un stub es un objeto que retorna respuestas predefinidas a llamadas específicas. No verifica cómo se le llama, solo proporciona datos de retorno. Úsalo cuando necesitas que una dependencia retorne un valor específico para tu test.

// Stub: simula una API que retorna datos fijos const userApiStub = { getUser: jest.fn().mockResolvedValue({ id: 1, name: 'Test User', email: 'test@example.com' }), updateUser: jest.fn().mockResolvedValue({ success: true }), }; // Uso en test it('muestra el nombre del usuario', async () => { const profile = new UserProfile(userApiStub); const result = await profile.load(1); expect(result.name).toBe('Test User'); }); // Prompt para la IA: "Crea stubs para estas dependencias: [lista]. Cada stub debe retornar datos realistas y soportar tanto respuestas exitosas como errores."

Mocks: verificación de comportamiento

Un mock no solo simula comportamiento, sino que también verifica que fue llamado correctamente: con los argumentos esperados, el número correcto de veces y en el orden adecuado. Úsalo cuando lo importante es verificar que tu código interactúa correctamente con una dependencia.

// Mock: verifica que se llamó correctamente const emailService = { send: jest.fn().mockResolvedValue(true), }; it('envía email de bienvenida al registrarse', async () => { const service = new UserService(emailService); await service.register({ email: 'new@test.com', name: 'New' }); expect(emailService.send).toHaveBeenCalledTimes(1); expect(emailService.send).toHaveBeenCalledWith( 'new@test.com', expect.stringContaining('Welcome'), expect.any(Object) ); }); // Prompt para la IA: "Genera tests con mocks que verifiquen: - Que la función llama a la DB con los parámetros correctos - Que se envía notificación solo cuando la operación es exitosa - Que se hace rollback si falla el segundo paso de la transacción"

Spies: observación sin interferencia

Un spy envuelve una función real para observar cómo se comporta sin cambiar su implementación. Es útil cuando quieres verificar que una función interna se llama correctamente pero necesitas que el código real se ejecute.

// Spy: observa sin modificar comportamiento const originalLog = console.log; const logSpy = jest.spyOn(console, 'log').mockImplementation(); it('loguea métricas de rendimiento', () => { processData(largeDataset); expect(logSpy).toHaveBeenCalledWith( expect.stringMatching(/processed \d+ items in \d+ms/) ); }); logSpy.mockRestore(); // Prompt para la IA: "Usa spies para testear que mi middleware de autenticación: - Llama a next() cuando el token es válido - Llama a res.status(401) cuando el token falta - Llama al logger con el userId en cada petición autenticada Sin mockear la lógica de verificación del token."

Testing de APIs y Bases de Datos

Las APIs y las bases de datos son componentes críticos que requieren un enfoque de testing específico. Los tests de APIs verifican que los endpoints respondan correctamente a diferentes peticiones, mientras que los tests de base de datos aseguran la integridad de los datos y el rendimiento de las queries.

Testing de APIs REST

Los tests de APIs deben cubrir todos los métodos HTTP, códigos de estado, validación de inputs y autenticación. Usa herramientas como supertest para hacer peticiones reales a tu servidor de test.

// Prompt para la IA: "Genera tests de integración completos para este endpoint Express usando supertest y una base de datos de test: [código del endpoint] Testea: - GET /api/users → 200 con lista de usuarios - GET /api/users/:id → 200 con usuario, 404 si no existe - POST /api/users → 201 con datos válidos, 400 con datos inválidos - PUT /api/users/:id → 200 actualizado, 404 si no existe - DELETE /api/users/:id → 204 eliminado, 404 si no existe - Autenticación: 401 sin token, 403 con token sin permisos - Paginación: ?page=2&limit=10 retorna resultados correctos - Búsqueda: ?search=juan filtra correctamente"

Testing de Bases de Datos

Los tests de base de datos verifican que las operaciones CRUD funcionan correctamente, que las transacciones mantienen la integridad y que las queries complejas retornan los resultados esperados. Siempre usa una base de datos separada para testing.

// Prompt para la IA: "Genera tests para este repositorio que usa Prisma con PostgreSQL: [código del repositorio] Requisitos: - Usa una base de datos de test separada - Limpia la DB antes de cada test (beforeEach) - Testea: * Crear registro con datos válidos * Crear registro con datos duplicados (constraint violation) * Leer con relaciones (include/join) * Actualizar parcialmente (solo algunos campos) * Eliminar en cascada (soft delete vs hard delete) * Transacciones: rollback si falla un paso intermedio * Queries complejas: filtros, ordenación, paginación - Usa factories para crear datos de test realistas"

Preguntas Frecuentes

¿Cuánta cobertura de tests necesito realmente?

No existe un número mágico, pero como referencia: el 80% de cobertura es un buen objetivo para la mayoría de proyectos. Lo más importante es que el 100% de tu lógica de negocio crítica esté testeada. Es mejor tener un 70% de cobertura con tests significativos que un 95% con tests triviales que solo verifican getters y setters. Enfócate en testear caminos críticos, edge cases y escenarios de error en lugar de perseguir un porcentaje arbitrario.

¿Los tests generados por IA son fiables?

Los tests generados por IA son un excelente punto de partida, pero siempre deben ser revisados por un humano. La IA puede generar tests que pasan incorrectamente (false positives), olvidar edge cases importantes del dominio de tu negocio, o crear mocks que no reflejan el comportamiento real de las dependencias. Revisa siempre que las assertions sean significativas (evita toBeTruthy genérico), que los datos de test sean realistas y que los mocks simulen correctamente los escenarios de error.

¿Cómo testeo código que depende de APIs externas?

Para APIs externas, usa una combinación de mocks en tests unitarios y tests de contracto para verificar que tu código maneja correctamente las respuestas de la API. En tests unitarios, mockea la API para retornar respuestas exitosas, errores de red, timeouts y respuestas malformadas. Para tests de integración más realistas, considera usar herramientas como MSW (Mock Service Worker) que interceptan peticiones HTTP a nivel de red. Nunca hagas tests que dependan de APIs externas reales en tu CI/CD, ya que serán lentos e inestables.

¿Qué hago cuando los tests son demasiado lentos?

Los tests lentos suelen deberse a: uso de base de datos real en vez de mocks, tests E2E donde bastarían tests unitarios, falta de paralelización o setup/teardown ineficiente. Primero, identifica los tests más lentos con --verbose o el reporter de tu framework. Luego, mueve tests de integración a unitarios donde sea posible, paraleliza con jest --maxWorkers, usa in-memory databases (SQLite en memoria) para tests de DB, y cachea dependencias en tu CI. Una suite de tests unitarios no debería tardar más de 30 segundos.