Escribir tus primeros casos de prueba

Resultados esperados, pruebas negativas, valores límite y clases de equivalencia.

Hasta ahora has enviado peticiones y has mirado lo que volvía. El testing empieza cuando decides, antes de enviar, qué debería volver, y luego comparas. Esa decisión por escrito es un caso de prueba. Esta lección trata de cómo escribirlos y de cómo elegir cuáles escribir, porque no puedes enviar todas las peticiones posibles.

Qué contiene un caso de prueba

Los equipos usan plantillas distintas, pero casi todas tienen las mismas partes:

ParteQué va en ellaEjemplo
IDUna referencia corta para poder hablar de élPROD-GET-03
TítuloQué se comprueba, en una líneaUn ID de producto desconocido devuelve 404
PrecondicionesQué tiene que ser cierto antes de empezarNo existe ningún producto con ID 9999
PeticiónMétodo, URL, cabeceras y cuerpoGET /api/Products/9999
Resultado esperadoEstado, cabeceras importantes, cuerpo404, cuerpo JSON cuyo mensaje nombra el ID 9999
Resultado realSe rellena al ejecutarloComo se esperaba

El resultado esperado es la parte que la gente escribe mal. "Devuelve un error" no es un resultado esperado, porque casi cualquier cosa lo cumple. Sé concreto con el código de estado, las cabeceras que importan (normalmente Content-Type, a veces cabeceras de caché o de ubicación) y el cuerpo: qué campos tienen que estar, con qué tipos y valores. Si no sabes decir cuál es la respuesta correcta, has encontrado una pregunta para el desarrollador o el responsable de producto, y merece la pena hacerla antes de probar nada.

¿De dónde sale el resultado esperado? De la documentación, del ticket que describía la funcionalidad, de cómo se comportan endpoints parecidos de la misma API y, a veces, del sentido común (un precio no debería ser negativo). Cuando estas fuentes no coinciden, apunta cuál seguiste.

Pruebas positivas y negativas

Una prueba positiva envía una petición válida y comprueba que funciona: el producto 1 existe, así que GET /api/Products/1 lo devuelve. Una prueba negativa envía algo que la API debería rechazar y comprueba que lo rechaza como es debido: el estado correcto, un mensaje útil y ningún efecto secundario.

Quien empieza escribe sobre todo pruebas positivas, porque sigue la descripción de la funcionalidad. Los desarrolladores suelen haber probado el camino positivo ellos mismos antes de entregar el trabajo. Las negativas son donde encuentras cosas. Como orientación, cuenta con escribir más pruebas negativas que positivas para cualquier endpoint que reciba entrada.

Clases de equivalencia

El endpoint de lista GET /api/Products acepta un parámetro limit. La referencia de la API dice que es el tamaño de página, de 1 a 100. Hay infinitos valores que podrías enviar. La partición en clases de equivalencia consiste en agrupar las entradas que el servidor debería tratar igual, y probar un valor de cada grupo en lugar de todos:

ClaseValor de ejemploEsperado
Válido, dentro del rangolimit=10Diez productos
Por debajo del rangolimit=-5Rechazado, o ajustado al mínimo
Por encima del rangolimit=500Rechazado, o ajustado a 100
No es un númerolimit=abcRechazado con 400
No es un enterolimit=2.5Rechazado con 400
Vacíolimit=Tratado como ausente
Ausentesin limitTodos los productos, según la documentación

Fíjate en las entradas "rechazado, o ajustado". La documentación no dice qué pasa fuera del rango, así que hay dos comportamientos razonables. Los dos se pueden defender. Lo que pruebas es que la API haga uno de ellos, de forma coherente, y que no se caiga ni devuelva algo sin sentido.

Valores límite

Los bugs se acumulan en los bordes de los rangos, porque ahí es donde los desarrolladores escriben < cuando querían decir <=. El análisis de valores límite consiste en probar el borde y un paso a cada lado. Para un rango de 1 a 100 eso da 0, 1, 2, 99, 100 y 101. Esto es lo que hace de verdad esta API con esos valores y con las otras clases. La cabecera X-Per-Page indica el tamaño de página que usó el servidor.

PeticiónEstadoElementos devueltosX-Per-Page
?limit=020011
?limit=120011
?limit=-120011
?limit=10020020 (todos)100
?limit=10120020 (todos)100
?limit=abc2002020
?limit=2.52002020
?limit=2002020
GET/api/Products?limit=0

Leyendo los resultados: los valores por debajo de 1 se ajustan a 1, los valores por encima de 100 se ajustan a 100, y cualquier cosa que no sea un entero se ignora como si limit no estuviera. Nada de esto está escrito, pero es coherente, así que probablemente es deliberado. Aun así, dos cosas merecen una nota en tu informe de pruebas. Que limit=0 devuelva un elemento sorprende: un cliente que pide cero elementos (para leer el total sin coste, por ejemplo) recibe un producto que no quería. Y que limit=abc se ignore en silencio significa que una errata en un cliente pasa desapercibida, cuando un 400 la habría atrapado.

Aquí también hay una lección sobre los datos de prueba. La API tiene solo 20 productos, así que limit=100 y limit=101 devuelven el mismo cuerpo. Por el cuerpo no podías saber que 101 se había ajustado. La cabecera lo mostraba. Cuando un límite es mayor que tus datos, busca otra señal como esta o crea suficientes registros para llegar al límite.

Los límites también se esconden en los IDs

La referencia indica que el id de /api/Products/{id} es un int32, un entero de 32 bits cuyo valor máximo es 2.147.483.647. Es un límite que vale la pena probar:

PeticiónEstadoCuerpo
/api/Products/20200El producto 20, el último
/api/Products/21404Mensaje de error JSON
/api/Products/0404Mensaje de error JSON
/api/Products/-1404Mensaje de error JSON
/api/Products/2147483647404Mensaje de error JSON
/api/Products/2147483648404Vacío
/api/Products/abc404Vacío
/api/Products/1.5404Vacío
/api/Products/01200El producto 1

Destacan dos cosas. Todo lo que no es un int32 válido recibe un 404 sin cuerpo alguno, mientras que los IDs válidos pero inexistentes reciben un error JSON. Y 01 se acepta como 1. Lo segundo es inofensivo aquí, pero en una API que guarda respuestas en caché por URL, /1 y /01 se guardarían por separado. Ninguno de los dos resultados es claramente erróneo. Los dos conviene conocerlos.

Un conjunto de casos de prueba para un endpoint

Juntándolo todo, un primer conjunto razonable de casos de prueba para GET /api/Products/{id} sería este. No es todo lo que podrías probar, pero cubre cada clase una vez y cada límite.

IDPeticiónResultado esperado
PROD-GET-01GET /api/Products/1200, JSON, id es 1, title es una cadena no vacía, price es un número mayor que 0
PROD-GET-02GET /api/Products/20200, el ID inicial más alto
PROD-GET-03GET /api/Products/21404, cuerpo de error JSON que nombra el ID 21
PROD-GET-04GET /api/Products/0404, cuerpo de error JSON
PROD-GET-05GET /api/Products/-1404, cuerpo de error JSON
PROD-GET-06GET /api/Products/abc400 o 404, con un cuerpo de error JSON
PROD-GET-07GET /api/Products/2147483648400 o 404, con un cuerpo de error JSON
PROD-GET-08HEAD /api/Products/1Mismo estado y cabeceras que GET, sin cuerpo
PROD-GET-09GET /api/Products/1, dos vecesCuerpos idénticos las dos veces

PROD-GET-06 y 07 fallan contra la API actual, porque el cuerpo está vacío. PROD-GET-08 también falla: HEAD /api/Products/1 devuelve 405 Method Not Allowed, mientras que los recursos más nuevos, como /api/Books/{id}inicia sesión para verlo, admiten HEAD. Si aquí se espera que haya HEAD es una pregunta para quien sea responsable de la API. No pasa nada. Un caso de prueba registra lo que debería ocurrir; un caso de prueba que falla es como descubres que no ocurre. PROD-GET-02 tiene un punto débil: si alguien borra el producto 20 o añade el 21, deja de significar lo que dice. La siguiente lección muestra cómo crear tus propios datos de prueba para que los tests no dependan de registros que controla otra persona.

Escribir un informe de bug

Cuando un caso de prueba falla, lo documentas. Un buen informe de bug permite a un desarrollador reproducir el problema en menos de un minuto sin preguntarte nada. Usando como ejemplo los 404 vacíos de arriba, compara primero las dos respuestas:

GET /api/Products/9999
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8

{"status":404,"error":"Not Found","message":"Product with ID 9999 does not exist."}
GET /api/Products/abc
HTTP/1.1 404 Not Found
Content-Length: 0

Un informe podría decir (aquí en inglés, como lo escriben muchos equipos):

Informe de bug
Title: GET /api/Products/{id} returns an empty 404 for non-numeric
       and out-of-range IDs, instead of the JSON error body

Environment: https://api.testingapis.com, 30 Sep 2026

Steps:
  curl -i https://api.testingapis.com/api/Products/9999
  curl -i https://api.testingapis.com/api/Products/abc
  curl -i https://api.testingapis.com/api/Products/2147483648

Expected:
  All three return 404 with Content-Type: application/json and a body
  like {"status":404,"error":"Not Found","message":"..."}, as documented
  under "Responses and errors" in the API reference.

Actual:
  9999        404, JSON body with a message
  abc         404, Content-Length: 0, no Content-Type, no body
  2147483648  404, empty body (one above the int32 maximum; 2147483647
              returns the JSON error)

Impact:
  Clients that parse every error body as JSON fail on the empty
  responses. The empty 404 also looks like "this URL does not exist"
  rather than "this product does not exist".

Notes:
  Probably the route only matches int32 values, so other input never
  reaches the controller. Could be intended; if so, the docs should say
  which errors have no body.

Qué hace útil este informe:

  • El título dice qué está mal y dónde, así que se entiende en una lista de cincuenta tickets.
  • Los pasos son comandos que se pueden pegar. Nada de "llama al endpoint de productos con un ID malo".
  • El comportamiento esperado apunta a una fuente (la documentación), así que no es solo tu preferencia.
  • Muestra el patrón en lugar de un único ejemplo. Tres peticiones, con una que funciona para comparar, dicen más que una petición que falla.
  • Indica el impacto en un cliente real. Los desarrolladores priorizan por impacto, y "incoherente" a secas rara vez se arregla.
  • Reconoce la incertidumbre. El comportamiento puede ser intencionado, y el informe dice qué lo resolvería en cualquier caso (arreglar el código o arreglar la documentación).

Ejercicio

Elige GET /api/Books/{id}inicia sesión para verlo o GET /api/Countries/code/{code}inicia sesión para verlo. Busca el endpoint en la referencia de la API y luego:

  • Enumera las clases de equivalencia del parámetro de ruta.
  • Elige los valores límite. Para los códigos de país, piensa en la longitud y en las mayúsculas y minúsculas.
  • Escribe al menos ocho casos de prueba con el formato de tabla de arriba, con resultados esperados concretos.
  • Ejecútalos con curl y rellena los resultados reales.

Si algo se comporta distinto de lo que esperabas, escribe un informe de bug con la estructura de arriba, incluyendo aquello de lo que no estás seguro.

Practica y ve más allá