Capítulo 05 de 10 · 10 min de lectura

De criterios a tests

Cuántos tests necesita un criterio, cuáles son los primeros que se quedan fuera y cómo ver, sin leer el código, qué parte del contrato no vigila nadie.

Un criterio, al menos un test

El capítulo anterior terminaba con once tests para cinco criterios. Falta explicar de dónde sale ese número, y cómo se sabe que no falta ninguno.

La regla de partida está en el AGENTS.md de acorta:

TDD: tests primero, uno al menos por criterio de la spec (el test nombra el identificador: URL-04). Ejecútalos y confirma que fallan antes de implementar. Después, lo mínimo para el verde. Refactor al final.

Tiene dos partes y la segunda es la que se olvida. «Uno al menos por criterio» dice cuánto hay que probar. «El test nombra el identificador» es lo que permite comprobarlo: si cada test dice a qué criterio responde, la pregunta «¿qué parte de la spec no tiene test?» la contesta un programa, sin que nadie lea el código.

¿Cuántos tests por criterio?

Los que el criterio pida. No hay cuota. Este es ALI-02, de la spec de acorta:

- **ALI-02** — Dado un alias de menos de 3 o de más de 32 caracteres, entonces error
  `alias: debe tener entre 3 y 32 caracteres`. Con 3 y con 32 se acepta. Se cuentan
  caracteres, no bytes: `ñu` tiene 2 y da este error; `ñus` tiene 3 y da el de ALI-03.

Y estos son los tests que lo nombran, tal como los imprime go test:

TestValidateAlias/ALI-02_2_caracteres
TestValidateAlias/ALI-02_3_caracteres_se_acepta
TestValidateAlias/ALI-02_32_caracteres_se_acepta
TestValidateAlias/ALI-02_33_caracteres
TestValidateAlias/ALI-02_se_cuentan_caracteres,_no_bytes:_ñu_son_2
TestValidateAlias/ALI-02_ñus_son_3_caracteres:_pasa_la_longitud_y_falla_ALI-03
TestValidateAlias/ALI-02_17_ñ_son_34_bytes_pero_17_caracteres:_falla_ALI-03,_no_la_longitud
TestValidateAlias/ALI-02_33_ñ_son_33_caracteres

Ocho tests para una frase. Salen de tres sitios:

  • Cada límite da dos tests, uno a cada lado. El criterio dice «menos de 3» y «más de 32»: se prueba con 2 y con 3, con 32 y con 33. Un fallo de uno, < donde iba <=, solo se ve justo en la frontera.
  • Cada ejemplo que el criterio nombra es un test. ñu y ñus están en la spec; pasan al test tal cual. Por eso conviene que la spec traiga ejemplos: son tests ya decididos.
  • Lo que separa este criterio del vecino. Diecisiete ñ son 34 bytes y 17 caracteres. Si el código contara bytes daría el error de longitud; contando caracteres da el de ALI-03. Ese test distingue las dos lecturas.

El encargo que recibió el agente lo pedía así:

Incluye los casos límite que la spec nombra (2048 bytes exactos, alias de 3 y de 32, caducidad igual al instante actual).

En acorta hay criterios con un solo test y uno con trece: API-06, el del cuerpo de una petición que no es lo que se espera. Hay muchas formas de mandar algo que no vale, y cada una es un caso.

Los casos negativos

Un caso negativo comprueba que algo no se acepta o que algo no ocurre. En la tabla de tests del alias hay 22 casos, y 14 esperan un error. Probar una validación consiste sobre todo en probar lo que rechaza.

Hay un segundo tipo, más fácil de olvidar: lo que no debe pasar además. Dos criterios de acorta lo dicen con cuatro palabras al final:

  • ALI-06: un alias ya usado da un error, «y no guarda nada».
  • RED-04: una petición HEAD responde igual que un GET, «sin cuerpo y sin sumar visita».

El error es fácil de comprobar. Lo otro pide mirar lo que no ha cambiado: que la lista de enlaces sigue igual, que el contador de visitas no se ha movido. Un test que solo mira la respuesta deja pasar un programa que contesta bien y guarda basura.

Y son los primeros que se quedan fuera, también de la spec. La de acorta se aprobó con 97 criterios, después de pasar por el agente que la redactó y por una revisión humana. Hoy tiene 100. Los tres que se añadieron:

Criterio Qué dice
ALM-09 Abrir la base de datos en una ruta que no se puede crear devuelve un error y no deja nada abierto
RES-05 Si no se puede sumar la visita, no se devuelve la URL
RED-07 Si el servicio falla por dentro, la respuesta es un 500 sin detalles

Los tres hablan de lo que pasa cuando algo va mal. Ninguno estaba en la spec, y los tres salieron de una pregunta que hizo un agente al escribir los tests. La revisión de una de esas fases lo resume así en la bitácora:

La spec solo describía los caminos limpios.

La matriz

Con cada test nombrando su criterio, se puede cruzar la spec con los tests. Esta es la matriz de acorta entera: sus 100 criterios, y en cada casilla cuántos de sus 284 tests lo nombran.

Buscar el identificador
  • 100criterios
  • 0sin ningún test
  • 47con un solo test
  • 28tests sin criterio
URL spec 002
COD spec 002
ALI spec 002
CAD spec 002
RES spec 002
VIS spec 002
LIS spec 002
BOR spec 002
ALM spec 003
RED spec 004
API spec 004
EST spec 004
CLI spec 005
WEB spec 006

0 ningún test lo nombra 1 depende de un solo test2 dos o más

Los 100 criterios de acorta tienen al menos un test que los nombra. Pulsa un criterio para ver cuáles.

ALI-028 tests · spec 002

Dado un alias de menos de 3 o de más de 32 caracteres, entonces error alias: debe tener entre 3 y 32 caracteres. Con 3 y con 32 se acepta. Se cuentan caracteres, no bytes: ñu tiene 2 y da este error; ñus tiene 3 y da el de ALI-03.

  • linkTestValidateAlias/ALI-02_17_ñ_son_34_bytes_pero_17_caracteres:_falla_ALI-03,_no_la_longitud
  • linkTestValidateAlias/ALI-02_2_caracteres
  • linkTestValidateAlias/ALI-02_3_caracteres_se_acepta
  • linkTestValidateAlias/ALI-02_32_caracteres_se_acepta
  • linkTestValidateAlias/ALI-02_33_caracteres
  • linkTestValidateAlias/ALI-02_33_ñ_son_33_caracteres
  • linkTestValidateAlias/ALI-02_ñus_son_3_caracteres:_pasa_la_longitud_y_falla_ALI-03
  • linkTestValidateAlias/ALI-02_se_cuentan_caracteres,_no_bytes:_ñu_son_2

Quita tests y mira la casilla: con el último que quites, el criterio se queda sin nadie que lo compruebe.

Los 28 tests que no nombran ningún criterio
  • linkTestErrorMessages/errores_centinela
  • linkTestErrorMessages/ValidationError_con_un_mensaje
  • linkTestErrorMessages/ValidationError_con_varios_mensajes_los_une_con_punto_y_coma
  • linkTestErrorMessages/ValidationError_se_recupera_con_errors.As
  • linkTestValidateAllErrorsAtOnce/entrada_correcta:_sin_mensajes
  • linkTestValidateAllErrorsAtOnce/los_tres_campos_fallan
  • linkTestValidateAllErrorsAtOnce/solo_alias_y_expires_at,_saltándose_url
  • linkTestValidateAllErrorsAtOnce/solo_url_y_expires_at
  • linkTestValidateAllErrorsAtOnce/un_alias_con_varios_fallos_da_solo_el_primero_(caracteres_antes_que_guion)
  • linkTestValidateAllErrorsAtOnce/un_alias_con_varios_fallos_da_solo_el_primero_(longitud)
  • linkTestValidateAllErrorsAtOnce/una_url_con_varios_fallos_da_solo_el_primero_(esquema_antes_que_dominio)
  • linkTestValidateEmptyValidOnErrors/fallan_los_tres
  • linkTestValidateEmptyValidOnErrors/solo_falla_el_alias
  • linkTestValidateEmptyValidOnErrors/solo_falla_expires_at
  • linkTestValidateEmptyValidOnErrors/solo_falla_la_url
  • linkTestValidateExpiresAtClockWithFraction
  • linkTestValidateReturnsCleanValid
  • shortenerTestCreate_CreatedAtEsElRelojEnUTCSinFracciones
  • shortenerTestCreate_EntradaInvalidaDaValidationErrorYNoGuardaNada/caducidad_pasada
  • shortenerTestCreate_EntradaInvalidaDaValidationErrorYNoGuardaNada/tres_errores_a_la_vez
  • shortenerTestCreate_EntradaInvalidaDaValidationErrorYNoGuardaNada/un_error
  • shortenerTestCreate_GuardaLaCaducidadValidada
  • shortenerTestFalloDelAlmacenSePropagaSinTraducirse/Create_con_alias
  • shortenerTestFalloDelAlmacenSePropagaSinTraducirse/Create:_un_error_de_Insert_que_no_es_ErrCodeTaken_no_se_reintenta
  • shortenerTestFalloDelAlmacenSePropagaSinTraducirse/Delete
  • shortenerTestFalloDelAlmacenSePropagaSinTraducirse/List
  • shortenerTestFalloDelAlmacenSePropagaSinTraducirse/Resolve
  • cmd/acortaTestCLI_UnknownFlagIsUsageError

Datos de acorta en el commit 15125e2: los nombres que imprimen go test y Vitest.

Tres cosas que mirar:

  • Ningún criterio está a cero. Es el mínimo que pide la regla, y se comprueba en un segundo.
  • 47 criterios dependen de un solo test, los de borde discontinuo. Pulsa la tercera casilla de la fila RES, que es RES-03, y quita su test. La casilla se pone en rojo. Es lo que pasa cuando alguien borra un test que molesta, y el capítulo sobre revisión volverá a ello: si era el único, la matriz lo ve. Si había más, no.
  • 28 tests no nombran ningún criterio. Abre la lista. Comprueban cosas que la spec dice en un párrafo, sin número: en qué orden se devuelven los errores, cómo se juntan varios mensajes. La matriz leída al revés señala reglas que existen y no tienen identificador. Un test sin criterio no sobra, pero pregunta dónde está escrito lo que comprueba.

Cincuenta y dos huecos que no lo eran

Pulsa ahora «Tal cual está en la spec». La matriz se llena de rojo: 52 criterios sin test.

Los tienen. Lo que cambia es cómo se busca. Las piezas de acorta las escribieron agentes distintos, y la regla «el test nombra el identificador» la cumplió cada uno a su manera:

Dónde Cómo se llama el test
link TestValidateAlias/ALI-02_2_caracteres
store, shortener, cmd/acorta TestALM03_CodigoRepetido
server TestAPI06_CuerpoInvalido/API-06_array
web WEB-01: envía alias si se ha escrito

En Go, el nombre de una función no admite guiones. El agente del dominio metió el identificador en el nombre de cada caso de la tabla, donde sí caben. Los otros lo pusieron en el nombre de la función, sin guion, y con guion en un comentario encima o en el mensaje de fallo. Buscando ALM-03 en el código aparece. En la salida de los tests, que es lo que lee un informe automático, no: ahí solo está TestALM03.

Se vio a mitad del proyecto. El agente de los casos de uso lo resolvió con comentarios y lo contó en su informe:

He añadido encima de cada una de las 27 funciones un comentario que empieza por su criterio con guion (// BOR-01: …). No he cambiado ningún nombre de función.

Y a partir de ahí los encargos lo pedían:

Cada test nuevo lleva su identificador con guion (RED-07).

La lección es de redacción de reglas. «Que lo nombre» dejaba abierto dónde y con qué forma, y esa forma es justo lo que va a buscar el programa que calcule la matriz. La regla de la plantilla lo dice ahora entero: el identificador escrito tal cual, en el nombre del test, y si el lenguaje no admite guiones, en un comentario justo encima.

Pídela, y luego compruébala

Todos los encargos del paso rojo de acorta terminaban pidiendo lo mismo en el informe:

una tabla criterio → test

Sirve para que el agente repase su propio trabajo antes de entregarlo. No sirve como prueba. El agente de los casos de uso corrigió su propio recuento en el informe siguiente:

Dije 27 tests y eran 26. Ahora hay 27 con el nuevo de RES-05.

Por eso la tabla del informe se lee, y la matriz se calcula aparte, con los nombres que imprimen los tests. Es una comprobación barata y no depende de lo que cuente nadie.

Lo que la matriz no dice

Dice si cada criterio tiene quien lo vigile. No dice si vigila bien.

Un test puede llamarse ALI-02 y no comprobar nada de ALI-02. Un criterio con cinco tests puede estar peor cubierto que otro con uno bueno. Y la matriz solo conoce los criterios escritos: lo que le falte a la spec le falta también a ella.

Para lo primero está la revisión, que tiene su capítulo. Para lo segundo, una técnica que rompe el código a propósito y mira si algún test se entera; es el último.

Lo que viene

Las reglas de estos capítulos (la spec primero, los tests antes que el código, el identificador en cada test) no sirven de nada si hay que repetírselas al agente en cada encargo. Viven en un fichero que lee él solo. El capítulo siguiente trata de ese fichero: qué poner, qué dejar fuera y qué cambia cuando falta una línea.