Ir al contenido

Cómo se verifica acá

Este proyecto tiene más de mil tests entre api/ y web/. Eso no dice nada por sí solo: un test que pasa con el mecanismo borrado no está probando el mecanismo, está probando otra cosa — o nada.

Lo que sigue no son buenas prácticas generales. Son cuatro técnicas que se adoptaron porque cada una atrapó un error real en este código, y las cuatro son baratas.


1 · Borrá el mecanismo y confirmá que el test falla

Sección titulada «1 · Borrá el mecanismo y confirmá que el test falla»

Antes de dar por buena una verificación, se rompe a propósito lo que dice proteger. Si la suite sigue verde, el test no lo estaba probando.

Ventana de terminal
# Se quita el tope contra el piso de precio…
sd 'Math.min\(nominal, lista - minimo\)' 'nominal' src/modules/ventas/precio.ts
pnpm vitest run src/modules/ventas # tiene que dar ≠ 0
# …y se restaura

Para un invariante de base de datos, se le quita el CHECK a aquazaku_test:

Ventana de terminal
psql "$TEST" -c 'ALTER TABLE clientes DROP CONSTRAINT clientes_credito_exige_verificacion;'
pnpm vitest run src/modules/clientes # tiene que dar ≠ 0
Dónde Qué pasaba
M6 · el redondeo del precio El test usaba montos «limpios» y pasaba igual sin Math.round. Buscando de verdad aparecieron 1.484 combinaciones donde cambia el resultado; la peor deja el comprobante sin cuadrar consigo mismo por un centavo
M6 · la atomicidad de la venta Dos tests decían probarla y pasaban con la transacción borrada: el rediseño hizo que esos caminos fallaran antes de escribir. Se renombraron a lo que verifican de verdad y se escribió el que sí prueba atomicidad
Toasts El ref que evita el aviso duplicado no estaba probado. El caso real era StrictMode, y sin él cada confirmación se veía dos veces en desarrollo
M7 · lógica muerta !corto && fraccion > 0.002 — la primera condición no decidía nada. Se borró en vez de escribirle un test
M16 · el botón reusado entre pasos En el paso 2 del alta, «Seguir sin teléfono» registraba al cliente y se salteaba el paso 3. El test de comportamiento pasaba con el defecto puesto: jsdom no implementa el envío implícito. El que muerde asserta el atributo type, y al ablarlo nombra el botón exacto
M16 · el teléfono plural Sacar telefonos del esquema de alta tiró la colección Bruno de 605/605 a 397/605. Sin esa corrida, la única evidencia era que los tests nuevos pasaban

Una captura de pantalla no es evidencia. El color que el CSS declara no es el que se ve compuesto, y una posición leída de una imagen escalada es una estimación.

// No: "se ve corrido". Sí:
{ isoLeft: 16, iconoLeft: 36, desalineacion: -20 }

Artefactos de medición en los que ya caímos

Sección titulada «Artefactos de medición en los que ya caímos»
  • Identificar por posición. querySelectorAll('nav').pop() devolvió un elemento distinto en cada llamada, y lotes[0] devolvió el lote de otro producto. Hay que identificar por aria-label, por id o por relación — nunca por índice.
  • Medir durante una transición. El grid del menú anima su ancho: medir justo después del foco reporta el estado inicial. Hay que esperar a que asiente.
  • Medir durante el Suspense. Los desbordes reportados eran del esqueleto de carga, no del contenido.
  • Un sd que no matchea. Dos «ablaciones» dieron verde porque el reemplazo nunca se aplicó. Desde entonces el script afirma que matcheó antes de correr los tests.

Una suite verde por módulo no es una suite verde. En M6 se commiteó con 19 tests caídos porque se leyó el número de tests sin mirar si había fallos: los tests del módulo pasaban aislados y rompían en conjunto.

Ventana de terminal
# Lo que importa es el código de salida, no el texto
pnpm test > /tmp/t.txt 2>&1; echo "EXIT=$?"

Lo mismo con la colección Bruno: se corre entera, contra una instancia levantada sobre aquazaku_test, nunca contra la base de desarrollo. Ver Exploración de la API.


4 · Leé el título del test y buscá cada sustantivo en el cuerpo

Sección titulada «4 · Leé el título del test y buscá cada sustantivo en el cuerpo»

La ablación tiene un punto ciego: no ve un mecanismo que el test nunca ejerce. Si el caso no toca el código, borrar ese código no lo rompe — el test sigue verde y la ablación no dice nada.

Ese hueco tiene una forma reconocible: un test cuyo nombre promete más de lo que el cuerpo hace.

// ❌ Se llama «manda cliente, teléfono y dirección en un solo viaje»
// y nunca escribe la dirección.
await usuario.type(campo(/Primer nombre/), 'Rosa')
await usuario.type(campo(/Teléfono/), '3001234567')
await usuario.click(boton(/Registrar/))
expect(enviado).toMatchObject({ primerNombre: 'Rosa', telefonos: [...] })

El chequeo es de dos segundos: leé el título, y buscá en el cuerpo cada sustantivo que nombra. Si dice «teléfono», tiene que haber un type sobre el campo de teléfono. Si dice «dirección», sobre la dirección. Lo que el título nombra y el cuerpo no toca: sale del título, o entra al cuerpo.


Las cuatro técnicas encontraron errores que ninguna revisión de código habría visto, porque todos los síntomas apuntaban al lugar equivocado: un test verde, un color que parecía bien, un archivo de tests que se caía por una ruta registrada en otro módulo, y un caso cuyo nombre decía cubrir lo que nunca tocó.

No hacen falta herramientas. Hace falta acordarse.