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.

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:
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.
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”.
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.
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?
Las invariantes son verdades que deben mantenerse antes y después de una llamada. Convierten la corrección vaga en aserciones concretas.
Ejemplos:
Cuando te enfocas en estos tres objetivos, obtienes menos pruebas pero cada una aporta más señal.
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.
Escribe el contrato en lenguaje claro, no en código, e incluye solo lo que puedas probar.
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”.
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:
| Dimension | Valid edge | Just outside | “Weird” value | Expected behavior |
|---|---|---|---|---|
| length | 0 | -1 | 10,000 | error 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).
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:
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.
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).
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:
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).
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:
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:
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.
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:
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:
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.
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.
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:
Si estas pasan, has cubierto los puntos de ruptura comunes sin llenar la suite con pruebas happy-path duplicadas.
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:
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.
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:
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.
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.
Happy-path tests mostly prove that your example still works. They tend to miss the stuff that breaks in production.
High-signal tests target:
Start with a tiny contract you can read in one breath:
Then generate tests from that contract, not from examples alone.
Test these first:
Pick one or two per input dimension so each test covers a unique risk.
A good failure-mode test proves two things:
If there’s a database write involved, always check what happened in storage after the failure.
Default approach: turn the invariant into an assertion on observable outcomes.
Examples:
expect(total).toBeGreaterThanOrEqual(0)Prefer checking both and , because many bugs hide in “returned OK but wrote the wrong thing.”
It’s worth keeping a happy-path test when it protects an invariant or a critical integration.
Good reasons to keep one:
Otherwise, trade it for boundary/failure tests that catch more classes of bugs.
Push for PHASE 1: plan only first.
Require the model to provide:
Only after you approve the plan should it generate code. This prevents “20 look-alike tests” output.
Default: mock only the boundary you don’t own (DB/network/clock), and keep everything else real.
To avoid over-mocking:
If a test breaks on refactor but behavior didn’t change, it’s often over-mocked or too implementation-coupled.
Use a simple deletion test:
Also scan for duplicates: