Prompting para Testing
Tests Significativos, No Solo Cobertura
El prompt por defecto para testing -- "escribe tests para este archivo" -- produce el resultado por defecto: tests superficiales que cubren el camino feliz, logran cobertura de líneas decente y no atrapan ningún bug real. Los tests significativos vienen de prompts significativos que especifican qué comportamientos probar, qué casos extremos cubrir y qué patrones de testing seguir.
Las herramientas de IA son excelentes generando boilerplate de tests, pero necesitan tu conocimiento del dominio para escribir tests que realmente importen. Tú sabes qué partes de tu código son frágiles, qué casos extremos han causado incidentes en producción y qué comportamientos son críticos para tu lógica de negocio. Tus prompts necesitan transferir ese conocimiento a la IA.
Especificando Casos de Test Explícitamente
El prompt de testing más efectivo lista los casos de test exactos que quieres cubrir. Esto elimina las conjeturas de la IA y asegura que los tests coincidan con tu entendimiento del código.
Malo:
Escribe tests para UserService.createUser()
Bueno:
Escribe tests unitarios para UserService.createUser() en
src/services/UserService.ts. Usa Vitest y sigue los patrones de nuestros
archivos de test existentes.
Casos de test a cubrir:
1. Entrada valida - crea usuario exitosamente:
- Entrada: { email: "new@test.com", password: "StrongPass1!", name: "Test User" }
- Verificar: usuario creado en BD, password hasheado (no almacenado en texto plano),
devuelve usuario sin campo passwordHash
2. Campos requeridos faltantes:
- Probar con email faltante, password faltante, nombre faltante (tests separados)
- Verificar: lanza ValidationError con nombre del campo especifico
3. Email duplicado:
- Crear un usuario, luego intentar crear otro con el mismo email
- Verificar: lanza ConflictError con mensaje sobre email duplicado
4. Password debil:
- Probar con "123", "password", "short" (menos de 8 chars), sin mayuscula, sin numero
- Verificar: lanza ValidationError con mensaje de requisitos de password
5. Fallo de conexion a base de datos:
- Mockear Prisma para que lance un error de conexion
- Verificar: lanza InternalError, no expone detalles de la base de datos
6. Normalizacion de email:
- Email de entrada: " Test@Example.COM "
- Verificar: email almacenado es "test@example.com" (limpiado y en minusculas)
Cada caso de test especifica la entrada, la acción y el resultado esperado. La IA producirá tests que coincidan con estas especificaciones exactamente.
El Prompt de Casos Extremos
Uno de los prompts de testing más valiosos le pide a la IA que identifique casos extremos que podrías haber pasado por alto.
Aqui esta mi funcion calculateShippingCost() en src/utils/shipping.ts:
[pegar codigo de la funcion]
Que casos extremos probablemente me faltan? Para cada caso extremo, explica:
1. Cual seria la entrada
2. Que sucede actualmente (probable bug)
3. Que deberia suceder
4. Un caso de test para atraparlo
Enfócate en:
- Valores limite (cero, negativos, valores maximos)
- Casos extremos de tipo (NaN, Infinity, strings vacios)
- Casos extremos de logica de negocio (umbral de envio gratis, internacional vs domestico)
- Problemas de concurrencia si se llama en paralelo
Este prompt aprovecha la habilidad de la IA para enumerar sistemáticamente casos extremos -- algo en lo que los humanos somos notoriamente malos porque tendemos a pensar en el camino feliz.
Prompting Dirigido por Tests
Puedes invertir el flujo de trabajo típico escribiendo las descripciones de los tests primero y luego pidiendo la implementación. Esto es desarrollo dirigido por tests vía prompts.
Quiero construir una utilidad de rate limiter. Aqui estan los casos de test
que debe pasar. Genera la implementacion en src/utils/rateLimiter.ts que
haga pasar todos estos tests:
describe('RateLimiter', () => {
it('permite requests bajo el limite')
it('bloquea requests sobre el limite')
it('reinicia el contador despues de la ventana de tiempo')
it('rastrea limites por clave unica (ej. direccion IP)')
it('devuelve el numero de requests restantes')
it('devuelve el tiempo hasta el reinicio en segundos')
it('maneja requests concurrentes correctamente')
it('limpia entradas expiradas para prevenir fugas de memoria')
})
El rate limiter debe:
- Ser una implementacion en memoria (sin Redis)
- Aceptar opciones: { maxRequests: number, windowMs: number }
- Exponer metodos: check(key: string), reset(key: string), getStatus(key: string)
- Devolver: { allowed: boolean, remaining: number, resetInMs: number }
Al definir los casos de test primero, te fuerzas a pensar en los requisitos antes de que se escriba código. La IA entonces produce una implementación que satisface tu contrato de tests.
Prompts de Tests de Integración
Los tests de integración requieren más contexto de setup, ejecución y cleanup que los tests unitarios. Tus prompts necesitan especificar las tres fases.
Escribe un test de integracion para el flujo de creacion de ordenes en
nuestra API Express. Usa Vitest con supertest. Archivo de test:
src/tests/integration/orders.test.ts.
Setup:
- Usar la base de datos de test (configurada en src/tests/setup.ts)
- Crear un usuario de prueba con un token JWT valido usando testHelpers.createAuthUser()
- Crear 3 productos de prueba en la base de datos usando testHelpers.createProduct()
- Limpiar la tabla de ordenes antes de cada test
Test: Crear una orden via POST /api/orders
- Body del request: { items: [{ productId, quantity: 2 }, { productId, quantity: 1 }] }
- Verificar respuesta 201 con ID de la orden
- Verificar que la orden existe en la base de datos con el total correcto
- Verificar que el inventario se decremento para cada producto
- Verificar que el estado de la orden es 'pending'
Test: Crear una orden con inventario insuficiente
- Establecer stock del producto en 1, intentar ordenar cantidad 5
- Verificar respuesta 400 con mensaje de error de inventario
- Verificar que no se creo ninguna orden
- Verificar que el inventario NO cambio (transaccion revertida)
Cleanup:
- Eliminar todas las ordenes, productos y usuarios de prueba despues del suite
- Cerrar la conexion a la base de datos
Seguir los mismos patrones que src/tests/integration/auth.test.ts para
estructura de test y helpers.
Igualando Patrones de Tests Existentes
Uno de los aspectos más importantes de la generación de tests es la consistencia con tu suite de tests existente. Estilos de test inconsistentes hacen el codebase más difícil de mantener.
Genera tests para el nuevo NotificationService en
src/services/NotificationService.ts. Iguala los patrones de testing de
nuestra suite de tests existente.
Archivos de referencia para patrones:
- src/services/__tests__/UserService.test.ts (para estructura de test de servicio)
- src/tests/mocks/prisma.ts (para enfoque de mocking de base de datos)
- src/tests/factories/user.ts (para factories de datos de prueba)
Nuestros patrones:
- Cada bloque describe agrupa por nombre de metodo
- Usar beforeEach para reiniciar mocks, no beforeAll
- Funciones factory para datos de prueba (createMockUser, createMockNotification)
- Mockear servicios externos en un directorio __mocks__
- Asertar con expect().toEqual para objetos, expect().toThrow para errores
- Nombrado de archivos de test: ServiceName.test.ts en directorio __tests__
Probar estos metodos: sendEmail, sendPush, sendBulk, getNotificationHistory
Anti-Patrones de Testing
"Escribe tests para este archivo" (Demasiado Vago)
Este prompt produce tests que reflejan la implementación en lugar de probar el comportamiento. La IA lee el código y escribe tests que verifican que el código hace lo que el código hace -- lo cual es circular y no atrapa bugs.
En su lugar, describe el comportamiento que quieres probar sin referenciar los detalles de implementación. "Verificar que createUser() rechaza passwords menores de 8 caracteres" prueba comportamiento. "Verificar que createUser() llama a validatePassword() con el argumento password" prueba implementación.
"Logra 100% de cobertura" (Gaming de Métricas)
Pedir objetivos de cobertura produce tests que ejecutan cada línea sin asertar nada significativamente. Obtienes tests como:
// Este "test" logra cobertura pero no prueba nada
it('llama a processOrder', () => {
processOrder(mockData);
// sin aserciones
});
En su lugar, especifica aserciones significativas para cada caso de test. La cobertura es un efecto secundario de testing exhaustivo, no un objetivo en sí mismo.
"Prueba todo" (Explosión de Alcance)
Escribe tests completos para toda la API
Esto produce tests superficiales para todo en lugar de tests profundos para las partes críticas. Enfoca tus prompts de testing en el código más arriesgado, más complejo o más crítico para el negocio.
Prompts de Mocking
Mockear dependencias externas es una de las partes más complicadas del testing. Sé explícito sobre qué mockear y cómo.
Escribe tests unitarios para OrderService.processOrder() que mockeen todas
las dependencias externas.
Dependencias a mockear:
- prisma (base de datos): Usar nuestro mock de src/tests/mocks/prisma.ts
Mockear prisma.order.create para que devuelva una orden falsa con id "order-123"
Mockear prisma.product.findMany para que devuelva productos de prueba
- stripeClient (pago): Crear un mock que:
En exito: devuelva { id: "pi_123", status: "succeeded" }
En card_declined: lance Stripe.errors.StripeCardError
En rate_limit: lance Stripe.errors.StripeRateLimitError
- emailService (notificaciones): Mockear sendOrderConfirmation para que resuelva
Rastrear si fue llamado y con que argumentos
Para cada test, especifica cuales mocks devuelven exito y cuales lanzan
errores. Probar la matriz completa: BD exito + pago exito, BD exito + pago
fallo, BD fallo (no deberia intentar pago).
Prompts de Testing Basado en Propiedades
Para funciones con propiedades matemáticas o lógicas bien definidas, pide tests basados en propiedades:
Escribe tests basados en propiedades para la funcion de calculo de descuento
en src/utils/pricing.ts usando fast-check.
Propiedades a verificar:
1. El descuento nunca es negativo (salida >= 0 para cualquier entrada valida)
2. El descuento nunca excede el precio original (salida <= entrada.price)
3. Porcentajes de descuento mas altos producen precios finales mas bajos (monotonico)
4. Aplicar un descuento del 0% devuelve el precio original exactamente
5. Aplicar un descuento del 100% devuelve 0
6. El calculo de descuento es conmutativo con la cantidad (precio * cant * descuento
es igual a precio * descuento * cant)
Generar entradas: precio (0.01 a 99999.99), cantidad (1 a 1000), porcentaje
de descuento (0 a 100).
Los tests basados en propiedades atrapan bugs que los tests basados en ejemplos no detectan porque prueban miles de entradas generadas aleatoriamente contra invariantes matemáticos.
El Prompt de Revisión de Tests
Después de generar tests, usa un prompt de revisión para encontrar brechas:
Revisa los tests en src/services/__tests__/PaymentService.test.ts.
Verificar:
1. Casos extremos faltantes (hay entradas que podrian romper el codigo pero no se prueban?)
2. Aserciones debiles (tests que ejecutan codigo pero no asertan significativamente el resultado)
3. Aislamiento de tests (algun test depende del orden de ejecucion o estado compartido?)
4. Tests de caminos de error faltantes (todas las ramas de error estan cubiertas?)
5. Riesgos de tests flaky (timers, valores aleatorios, dependencias externas que no estan completamente mockeadas)
Para cada problema encontrado, sugiere el caso de test especifico a agregar o modificar.
Este meta-prompt atrapa las brechas que el prompt de generación original no detectó, creando una suite de tests más robusta a través de iteración.