Saltar al contenido
Lección 7 de 12

Prompting para Testing

9 min read

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 lineas decente y no atrapan ningun bug real. Los tests significativos vienen de prompts significativos que especifican que comportamientos probar, que casos extremos cubrir y que 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. Tu sabes que partes de tu codigo son fragiles, que casos extremos han causado incidentes en produccion y que comportamientos son criticos para tu logica de negocio. Tus prompts necesitan transferir ese conocimiento a la IA.

Especificando Casos de Test Explicitamente

El prompt de testing mas 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 codigo.

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 accion y el resultado esperado. La IA producira tests que coincidan con estas especificaciones exactamente.

El Prompt de Casos Extremos

Uno de los prompts de testing mas valiosos le pide a la IA que identifique casos extremos que podrias 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

Enfocate 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 sistematicamente 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 tipico escribiendo las descripciones de los tests primero y luego pidiendo la implementacion. Esto es desarrollo dirigido por tests via 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 codigo. La IA entonces produce una implementacion que satisface tu contrato de tests.

Prompts de Tests de Integracion

Los tests de integracion requieren mas contexto de setup, ejecucion 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 mas importantes de la generacion de tests es la consistencia con tu suite de tests existente. Estilos de test inconsistentes hacen el codebase mas dificil 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 implementacion en lugar de probar el comportamiento. La IA lee el codigo y escribe tests que verifican que el codigo hace lo que el codigo hace -- lo cual es circular y no atrapa bugs.

En su lugar, describe el comportamiento que quieres probar sin referenciar los detalles de implementacion. "Verificar que createUser() rechaza passwords menores de 8 caracteres" prueba comportamiento. "Verificar que createUser() llama a validatePassword() con el argumento password" prueba implementacion.

"Logra 100% de cobertura" (Gaming de Metricas)

Pedir objetivos de cobertura produce tests que ejecutan cada linea 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 si mismo.

"Prueba todo" (Explosion de Alcance)

Escribe tests completos para toda la API

Esto produce tests superficiales para todo en lugar de tests profundos para las partes criticas. Enfoca tus prompts de testing en el codigo mas arriesgado, mas complejo o mas critico para el negocio.

Prompts de Mocking

Mockear dependencias externas es una de las partes mas complicadas del testing. Se explicito sobre que mockear y como.

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 matematicas o logicas 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 matematicos.

El Prompt de Revision de Tests

Despues de generar tests, usa un prompt de revision 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 generacion original no detecto, creando una suite de tests mas robusta a traves de iteracion.