7 min

Prompt de Claude Code para generar pruebas de casos límite

Aprende un prompt de Claude Code para generar pruebas que detecten fallos de verdad, enfocándose en límites, invariantes y modos de fallo en lugar de solo happy paths.

Prompt de Claude Code para generar pruebas de casos límite

Por qué generar solo pruebas happy-path es una pérdida de tiempo

Las suites de pruebas generadas automáticamente suelen impresionar: docenas de tests, mucho código de configuración y cada nombre de función aparece en algún lugar. Pero muchas de esas pruebas son solo comprobaciones de “funciona cuando todo es normal”. Pasan fácilmente, rara vez detectan fallos y aún así cuestan tiempo para leer y mantener.

Con un prompt típico de Claude Code para generar pruebas, el modelo tiende a reflejar los ejemplos que ve. Obtienes variaciones que parecen diferentes pero cubren el mismo comportamiento. El resultado es una gran suite con cobertura superficial donde importa.

Las pruebas de alto valor son diferentes. Son el conjunto pequeño que habría detectado el incidente del mes pasado. Fallan cuando el comportamiento cambia de forma riesgosa y se mantienen estables ante refactorizaciones inocuas. Una prueba de alto valor puede valer por veinte comprobaciones de “devuelve el valor esperado”.

La generación de happy-path de bajo valor suele mostrar algunos síntomas claros:

  • Muchas pruebas difieren solo en etiquetas de entrada, no en lo que puede romper.
  • Las aserciones son superficiales (“no es nulo”, “status es 200”) en vez de comprobar el significado.
  • La configuración pesa más que el comportamiento probado, así que la gente deja de actualizar tests.
  • La cobertura parece alta, pero los casos límite están intactos.

Imagina una función que aplica un código de descuento. Las pruebas happy-path confirman que “SAVE10” reduce el precio. Los bugs reales se esconden en otro sitio: precios 0 o negativos, códigos caducados, bordes de redondeo o límites máximos de descuento. Esos son los casos que causan totales erróneos, clientes enfadados y rollbacks a medianoche.

El objetivo es pasar de “más pruebas” a “mejores pruebas” apuntando a tres objetivos: límites, modos de fallo e invariantes.

Los tres objetivos: límites, modos de fallo, invariantes

Si quieres pruebas unitarias de alto valor, deja de pedir “más pruebas” y empieza a pedir tres tipos específicos. Este es el núcleo de un prompt de Claude Code que produce cobertura útil en lugar de un montón de comprobaciones “funciona con entrada normal”.

1) Límites (donde se esconden los bugs)

Los límites son los bordes de lo que el código acepta o produce. Muchos defectos reales son off-by-one, estados vacíos o problemas de timeout que nunca aparecen en un happy path.

Piensa en términos de mínimos y máximos (0, 1, longitud máxima), vacío vs presente ("", [], nil), off-by-one (n-1, n, n+1) y límites temporales (cerca del corte).

Ejemplo: si una API acepta “hasta 100 elementos”, prueba 100 y 101, no solo 3.

2) Modos de fallo (demuestra que falla de forma segura)

Los modos de fallo son las formas en que el sistema puede romperse: entradas malas, dependencias faltantes, resultados parciales o errores aguas arriba. Las buenas pruebas de modos de fallo comprueban el comportamiento bajo estrés, no solo la salida en condiciones ideales.

Ejemplo: cuando falla una llamada a la base de datos, ¿la función devuelve un error claro y evita escribir datos parciales?

3) Invariantes (reglas que siempre deben cumplirse)

Las invariantes son verdades que deben mantenerse antes y después de una llamada. Convierten la corrección vaga en aserciones concretas.

Ejemplos:

  • “El balance nunca es negativo” tras cualquier intento de retiro.
  • “Los IDs son únicos” incluso si creas ítems rápidamente.
  • “En error, no hay cambios de estado” (no filas nuevas, no flags alterados).

Cuando te enfocas en estos tres objetivos, obtienes menos pruebas pero cada una aporta más señal.

Preparación: extrae un contrato pequeño antes de escribir pruebas

Si pides pruebas demasiado pronto, normalmente obtienes un montón de comprobaciones educadas de “funciona como se espera”. Una solución simple es escribir primero un contrato pequeñito y luego generar pruebas a partir de ese contrato. Es la forma más rápida de convertir un prompt de Claude Code en algo que encuentre bugs reales.

Un contrato útil es lo bastante corto para leerse de un tirón. Apunta a 5–10 líneas que respondan tres preguntas: qué entra, qué sale y qué más cambia.

Plantilla de contrato de 5–10 líneas

Escribe el contrato en lenguaje claro, no en código, e incluye solo lo que puedas probar.

  • Inputs: tipos, rangos permitidos y qué cuenta como “vacío” o “faltante”.
  • Output: valor de retorno o forma de error, y qué garantiza el “éxito”.
  • Efectos secundarios: cambios de estado, filas en BD, llamadas de red, ficheros, logs.
  • Suposiciones: cosas que los llamadores suelen fallar (zona horaria, codificación, auth, orden).
  • “Debe nunca ocurrir”: crash, pérdida de datos silenciosa, doble cargo, escrituras parciales.

Una vez que tengas eso, escanéalo en busca de dónde la realidad puede romper tus suposiciones. Esos se convierten en casos límite (min/max, cero, overflow, cadenas vacías, duplicados) y modos de fallo (timeouts, permiso denegado, violaciones de unicidad, entrada corrupta).

Aquí hay un ejemplo concreto para una función como reserveInventory(itemId, qty):

El contrato podría decir qty debe ser un entero positivo, la función debe ser atómica y nunca debe crear stock negativo. Eso sugiere instantáneamente pruebas de alto valor: qty = 0, qty = 1, qty mayor que lo disponible, llamadas concurrentes y un error forzado en la base de datos a mitad de la operación.

Si usas una herramienta de vibe-coding como Koder.ai, el mismo flujo aplica: escribe el contrato en el chat primero y luego genera pruebas que ataquen directamente límites, modos de fallo y la lista de “debe nunca ocurrir”.

Patrón de prompt: el blueprint para pruebas de alto valor

Usa este prompt de Claude Code cuando quieras menos pruebas, pero cada una que haga su trabajo. El movimiento clave es forzar un plan de pruebas primero y luego generar código solo después de aprobar el plan.

You are helping me write HIGH-SIGNAL unit tests.

Context
- Language/framework: <fill in>
- Function/module under test: <name + short description>
- Inputs: <types, ranges, constraints>
- Outputs: <types + meaning>
- Side effects/external calls: <db, network, clock, randomness>

Contract (keep it small)
1) Preconditions: <what must be true>
2) Postconditions: <what must be true after>
3) Error behavior: <how failures are surfaced>

Task
PHASE 1 (plan only, no code):
A) Propose 6-10 tests max. Do not include “happy path” unless it protects an invariant.
B) For each test, state: intent, setup, input, expected result, and WHY it is high-signal.
C) Invariants: list 3-5 invariants and how each will be asserted.
D) Boundary matrix: propose a small matrix of boundary values (min/max/empty/null/off-by-one/too-long/invalid enum).
E) Failure modes: list negative tests that prove safe behavior (no crash, no partial write, clear error).
Stop after PHASE 1 and ask for approval.

PHASE 2 (after approval):
Generate the actual test code with clear names and minimal mocks.

Un truco práctico es requerir la matriz de límites como una tabla compacta, para que las brechas sean obvias:

DimensionValid edgeJust outside“Weird” valueExpected behavior
length0-110,000error vs clamp vs accept

Si Claude propone 20 pruebas, replantea. Pídele que fusione casos similares y mantenga solo los que realmente atraparían un bug real (off-by-one, tipo de error equivocado, pérdida de datos silenciosa, invariante rota).

Paso a paso: ejecuta el prompt y convierte la salida en pruebas

Turn prompts into production code
Ship web, backend, or mobile apps from chat, then lock behavior down with tests.

Comienza con un contrato pequeño y concreto para el comportamiento que quieres. Pega la firma de la función, una breve descripción de entradas y salidas, y cualquier prueba existente (aunque solo sean happy-path). Esto mantiene al modelo anclado en lo que el código realmente hace, no en lo que adivina.

A continuación, pide una tabla de riesgos antes de solicitar cualquier código de prueba. Exige tres columnas: casos límite (bordes de la entrada válida), modos de fallo (entrada mala, datos faltantes, timeouts) e invariantes (reglas que siempre deben mantenerse). Añade una frase por fila: “por qué esto puede romperse.” Una tabla simple revela huecos más rápido que un montón de archivos de prueba.

Luego elige el conjunto más pequeño de pruebas donde cada una tenga un propósito único para encontrar bugs. Si dos pruebas fallan por la misma razón, conserva la más fuerte.

Una regla práctica de selección:

  • Mantén pruebas que toquen diferentes límites (min, max, vacío, off-by-one).
  • Mantén pruebas que demuestren comportamiento seguro ante fallos (error claro, sin escritura parcial, sin crash).
  • Mantén pruebas que afiancen una invariante (orden, totales, idempotencia, sin duplicados).
  • Elimina pruebas que solo repitan “funciona con entrada normal”.

Finalmente, exige una explicación corta por prueba: qué bug atraparía si falla. Si la explicación es vaga (“valida comportamiento”), probablemente la prueba es de bajo valor.

Cómo codificar invariantes en aserciones

Una invariante es una regla que debe mantenerse sin importar la entrada válida que pases. Con las pruebas basadas en invariantes, primero escribes la regla en lenguaje claro y luego la conviertes en una aserción que pueda fallar ruidosamente.

Elige 1 o 2 invariantes que realmente te protejan de bugs reales. Las buenas invariantes suelen ser sobre seguridad (sin pérdida de datos), consistencia (mismas entradas, mismos resultados) o límites (nunca exceder topes).

Convierte una invariante en una comprobación demostrable

Escribe la invariante como una frase corta, luego decide qué evidencia puede observar tu prueba: valores de retorno, datos almacenados, eventos emitidos o llamadas a dependencias. Las aserciones fuertes comprueban tanto el resultado como los efectos secundarios, porque muchos bugs se esconden en “devolvió OK, pero escribió mal”.

Por ejemplo, si tienes una función que aplica un cupón a un pedido:

  • Invariante: el total final nunca es negativo.
  • Invariante: aplicar el mismo cupón dos veces no descuenta doble.

Ahora codifícalas como aserciones que midan algo concreto:

expect(result.total).toBeGreaterThanOrEqual(0)
expect(db.getOrder(orderId).discountCents).toBe(originalDiscountCents)

Evita aserciones vagas como “devuelve el resultado esperado”. Asegura la regla específica (no negativo) y el efecto secundario concreto (descuento guardado una vez).

Añade una nota de contraejemplo para mantener la prueba afilada

Para cada invariante, añade una nota breve en la prueba sobre qué datos la violarían. Esto evita que la prueba se convierta en una comprobación de happy-path con el tiempo.

Un patrón simple que perdura:

  • Pon la invariante en el nombre de la prueba.
  • Aserta la invariante sobre la salida.
  • Aserta el efecto secundario clave (o su ausencia).
  • Añade un comentario describiendo un caso que la violaría (por ejemplo, un cupón enorme o aplicación duplicada).

Modos de fallo: escribe pruebas que demuestren comportamiento seguro

Las pruebas de alto valor suelen ser las que confirman que tu código falla de forma segura. Si un modelo solo escribe pruebas happy-path, aprendes casi nada sobre cómo se comporta la función cuando entradas y dependencias se vuelven problemáticas.

Empieza definiendo qué significa “seguro” para esta característica. ¿Devuelve un error tipado? ¿Cae de forma degradada a un valor por defecto? ¿Reintenta una vez y luego para? Escribe ese comportamiento esperado en una frase y haz que las pruebas lo demuestren.

Cuando pidas a Claude Code pruebas de modos de fallo, mantén el objetivo estricto: cubre las formas en que el sistema puede romperse y aserta la respuesta exacta que quieres. Una línea útil es: “Prefiere menos pruebas con aserciones más fuertes a muchas pruebas superficiales.”

Categorías de fallo que suelen producir las mejores pruebas:

  • Entradas malas: formatos inválidos, campos requeridos faltantes, valores fuera de rango
  • Fallos de dependencias: timeouts, 500s, respuestas vacías, payloads corruptos
  • Problemas de orden: eventos fuera de orden, duplicados, escrituras parciales
  • Concurrencia: actualizaciones en carrera, comprobaciones de idempotencia
  • Comportamiento de recuperación: cuándo devuelves un error vs fallback vs reintento

Ejemplo: tienes un endpoint que crea un usuario y llama a un servicio de email para enviar un mensaje de bienvenida. Una prueba de bajo valor comprueba “devuelve 201”. Una prueba de fallo de alto valor comprueba que si el servicio de email hace timeout, o bien (a) sigues creando el usuario y devuelves 201 con una flag “email_pending”, o (b) devuelves un 503 claro y no creas el usuario. Escoge un comportamiento y aserta tanto la respuesta como los efectos secundarios.

También prueba lo que no filtras. Si falla la validación, asegúrate de que no se escriba nada en la base de datos. Si una dependencia devuelve un payload corrupto, asegúrate de no lanzar una excepción sin capturar ni devolver trazas internas.

Trampas comunes que generan pruebas de bajo valor

Get rewarded for sharing
Share what you learn about high-signal tests and earn credits for your Koder.ai account.

Los conjuntos de pruebas de bajo valor suelen aparecer cuando el modelo es recompensado por volumen. Si tu prompt de Claude Code pide “20 pruebas unitarias”, a menudo obtienes pequeñas variaciones que parecen exhaustivas pero que no detectan nada nuevo.

Trampas comunes:

  • Tests que se parecen: la misma prueba de “entrada válida” repetida con diferentes cadenas o números.
  • Tests que reflejan el código: asertar pasos privados o llamadas a helpers en vez de comportamiento observable.
  • Mockearlo todo: reemplazar DB, reloj, red y config a la vez.
  • Aserciones débiles: solo comprobar “sin error”, “no nulo” o “status 200”.
  • Estado compartido sucio: dejar datos sembrados, globals modificadas o valores cacheados.

Ejemplo: imagina una función “create user”. Diez pruebas happy-path podrían variar la cadena de email y aún así perder lo importante: rechazar emails duplicados, manejar contraseña vacía y garantizar que los IDs de usuario devueltos sean únicos y estables.

Guardarraíles que ayudan en la revisión:

  • Exige que cada prueba nombre el riesgo que cubre (límite, modo de fallo o invariante).
  • Evita comprobaciones dependientes de implementación a menos que cambien el comportamiento observable.
  • Mantén los mocks al mínimo y permite algunas pruebas pequeñas que toquen la integración real cuando sea factible.
  • Exige aserciones fuertes: salidas exactas, cambios de estado y tipos/mensajes de error.
  • Añade reglas de limpieza para que las pruebas no dependan del orden.

Ejemplo: convertir una característica en un conjunto pequeño y sólido de pruebas

Imagina una característica: aplicar un código de cupón en el checkout.

Contrato (pequeño y testeable): dada una subtotal del carrito en céntimos y un cupón opcional, devuelve un total final en céntimos. Reglas: los cupones porcentuales redondean hacia abajo al céntimo más cercano, los cupones fijos restan una cantidad fija y los totales nunca bajan de 0. Un cupón puede ser inválido, caducado o ya usado.

No pidas “tests para applyCoupon()”. Pide pruebas de casos límite, modos de fallo e invariantes vinculadas a este contrato.

Límites para forzar comportamiento en el borde

Elige entradas que suelen romper validaciones o cálculos: un código de cupón vacío, subtotal = 0, subtotal justo por debajo y por encima de un mínimo de gasto, un descuento fijo mayor que el subtotal y un porcentaje como 33% que produce redondeo.

Modos de fallo para demostrar comportamiento seguro

Asume que la búsqueda de cupones puede fallar y que el estado puede estar mal: el servicio de cupones está caído, el cupón está caducado o ya fue canjeado por este usuario. La prueba debe demostrar qué ocurre después (cupón rechazado con error claro, total sin cambios).

Un conjunto mínimo y de alto valor de pruebas (5 tests) y qué atrapa cada una:

  • Rechazar código vacío o solo espacios: atrapa bugs que aceptan blanco como válido y problemas de trim.
  • Redondeo con cupón porcentual (subtotal 101, 33%): atrapa errores de redondeo y off-by-one en céntimos.
  • Descuento fijo mayor que el subtotal (subtotal 500, descuento 1000): prueba la invariante de que el total nunca sea negativo.
  • Límite de gasto mínimo (subtotal 999 vs 1000): atrapa lógica de comparación equivocada (< vs <=).
  • Falla en la búsqueda del cupón o timeout: prueba fallback seguro (no aplicar descuento) y manejo de errores estable.

Si estas pasan, has cubierto los puntos de ruptura comunes sin llenar la suite con pruebas happy-path duplicadas.

Lista rápida de verificación para pruebas AI-generadas de alto valor

Plan before you generate
Draft a clear contract and test matrix first, then generate code with confidence.

Antes de aceptar lo que genere el modelo, haz una revisión rápida. El objetivo son pruebas que protejan de un bug específico y probable.

Usa esta lista como filtro:

  • Límites por entrada: para cada campo de entrada (cadenas, IDs, timestamps, flags), incluye al menos un caso límite (vacío vs solo espacios, longitud máxima, cero vs negativo, campos opcionales ausentes, uno pasado del límite).
  • Fallos de dependencias: incluye al menos una prueba donde una dependencia se comporte mal (timeout BD, API externa 500, token de auth caducado). Prueba comportamiento seguro (error claro, sin escrituras parciales).
  • Invariantes con aserciones fuertes: elige 1–3 reglas que siempre deben cumplirse y asértalas directamente. Evita aserciones vagas como “respuesta ok”.
  • Un bug único por prueba: lee cada título y pregúntate “¿qué bug exacto atraparía esto?” Si dos pruebas responden la misma pregunta, fusiónalas.
  • Prueba de eliminación: intenta borrar una prueba. Si no se pierde nada significativo (ningún límite, modo de fallo o invariante), no merecía estar.

Un truco práctico después de generar: renombra las pruebas a “should <comportamiento> when <condición límite>” y “should not <mal resultado> when <fallo>”. Si no puedes renombrarlas limpiamente, no están enfocadas.

Si construyes con Koder.ai, esta lista encaja bien con snapshots y rollback: genera pruebas, ejecútalas y revierte si el nuevo set añade ruido sin mejorar la cobertura.

Próximos pasos: convertir esto en un flujo repetible

Trata tu prompt como un arnés reutilizable, no como una petición puntual. Guarda un blueprint (el que fuerza límites, modos de fallo e invariantes) y reutilízalo para cada función, endpoint o flujo de UI.

Un hábito simple que mejora resultados rápido: pide una frase por prueba explicando qué bug atraparía. Si esa frase es genérica, la prueba probablemente es ruido.

Mantén una lista viva de invariantes de dominio para tu producto. No la guardes en la cabeza. Añádela cada vez que encuentres un bug real.

Un flujo ligero que puedes repetir:

  • Extrae un contrato pequeño: inputs, outputs, manejo de errores y 3–5 invariantes.
  • Ejecuta el prompt blueprint y solicita límites, modos de fallo, invariantes y justificaciones de una línea.
  • Implementa solo las 5–10 pruebas principales que cubran riesgos distintos.
  • Refactoriza y vuelve a ejecutar el prompt para ver qué riesgos nuevos aparecen.
  • Podar duplicados y conservar las pruebas que habrían atrapado incidentes pasados.

Si construyes apps vía chat, ejecuta este ciclo dentro de Koder.ai (koder.ai) para que el contrato, el plan y las pruebas generadas vivan en un mismo lugar. Cuando un refactor cambie el comportamiento inesperadamente, los snapshots y el rollback facilitan comparar e iterar hasta que tu conjunto de alto valor se mantenga estable.

Preguntas frecuentes

How many unit tests should I generate per function?

Default: aim for a small set that would catch a real bug.

A quick cap that works well is 6–10 tests per unit (function/module). If you need more, it usually means your unit is doing too much or your contract is unclear.

What’s wrong with generating lots of happy-path tests?

Happy-path tests mostly prove that your example still works. They tend to miss the stuff that breaks in production.

High-signal tests target:

  • Boundaries (0/1/max, empty/null, off-by-one)
  • Failure modes (timeouts, invalid inputs, dependency errors)
  • Invariants (rules that must always hold, like “no partial write on error”)
What should I write down before asking an AI to generate tests?

Start with a tiny contract you can read in one breath:

  • Inputs: types, allowed ranges, what counts as empty/missing
  • Outputs: success shape and error shape
  • Side effects: what can be written/changed (DB, files, network)
  • “Must never happen”: crash, silent data loss, double charge, partial writes

Then generate tests from that contract, not from examples alone.

Which boundary cases are usually worth testing?

Test these first:

  • Min/max values (0, 1, max, max+1)
  • Empty vs present ("", [], null/nil)
  • Off-by-one (n-1, n, n+1)
  • Formatting edges (whitespace-only strings, leading zeros)
  • Time cutoffs (just before/after expiry)

Pick one or two per input dimension so each test covers a unique risk.

How do I write a good “failure mode” test instead of a shallow one?

A good failure-mode test proves two things:

  1. The function returns a clear, expected error (type/message/status).
  2. It fails safely:
  • no partial state changes
  • no leaked internal details
  • no retries or side effects you didn’t intend

If there’s a database write involved, always check what happened in storage after the failure.

How do I turn an invariant into a test assertion?

Default approach: turn the invariant into an assertion on observable outcomes.

Examples:

  • “Total never negative” → expect(total).toBeGreaterThanOrEqual(0)
  • “On error, no state changes” → assert no new rows / no flags flipped
  • “Idempotent” → call twice and assert the second call doesn’t change state

Prefer checking both return value and side effects, because many bugs hide in “returned OK but wrote the wrong thing.”

When is a happy-path test still worth writing?

It’s worth keeping a happy-path test when it protects an invariant or a critical integration.

Good reasons to keep one:

  • It asserts a key invariant on normal input (e.g., rounding rules)
  • It locks down an API contract that callers rely on
  • It guards against a past incident regression

Otherwise, trade it for boundary/failure tests that catch more classes of bugs.

What should I ask the model to output before generating test code?

Push for PHASE 1: plan only first.

Require the model to provide:

  • 6–10 proposed tests max
  • For each: intent, setup, input, expected result, why it’s high-signal
  • A small boundary matrix
  • A failure-mode list
  • 3–5 invariants and how to assert them

Only after you approve the plan should it generate code. This prevents “20 look-alike tests” output.

How do I avoid tests that are brittle because they mock too much?

Default: mock only the boundary you don’t own (DB/network/clock), and keep everything else real.

To avoid over-mocking:

  • Don’t mock internal helpers just to mirror implementation
  • Use a real in-memory version when feasible, or a small fake with clear behavior
  • Mock the clock/randomness only when it affects the assertion

If a test breaks on refactor but behavior didn’t change, it’s often over-mocked or too implementation-coupled.

How can I quickly tell if an AI-generated test is low-value?

Use a simple deletion test:

  • If you delete the test and lose no boundary, no failure mode, and no invariant, it didn’t earn its place.

Also scan for duplicates:

  • If two tests would fail for the same bug, keep the one with the stronger assertion.
  • If assertions are just “not null” or “status 200,” strengthen them or remove the test.

Related posts