El test que pasa sin probar nada
Un mock sin configurar devolvía null, el código tiraba una excepción, un catch la escondía y el test seguía en verde. Cuatro formas reales en que una suite verde deja de significar algo, y cómo detectarlas.
Un test en verde dice una de dos cosas: que el código hace lo que se espera, o que el test no llegó a mirar. Las dos se ven exactamente igual en el pipeline.
Este post junta cuatro casos reales —encontrados en revisiones de código, sobre tests míos y de otras personas— en los que la suite estaba verde y no validaba lo que su nombre prometía.
El caso: la excepción que el catch se comía
El componente cargaba varias colecciones de reglas desde una base y las dejaba en memoria. Un cambio agregó cuatro colecciones nuevas. El test de esa carga usaba un doble de prueba para la base y configuraba la respuesta de tres colecciones de las cuatro.
La cadena completa
- Para la colección sin configurar, el doble devuelve
nullen vez de una lista vacía: es el comportamiento por defecto de un mock cuando nadie le dijo qué contestar. - El código de carga hace
.stream()sobre ese resultado y tira una excepción de puntero nulo. - La carga de cada colección está envuelta en un
try/catch—razonable: no querés que una colección rota tumbe el arranque de la aplicación—. - El catch registra el error y sigue.
- El test pasa. Nadie afirma nada sobre esa colección, así que no hay assert que falle.
El resultado: un test que corría con una excepción silenciada en cada ejecución y que no validaba el mapeo de una de las cuatro colecciones nuevas —justamente la parte nueva del cambio—. Y el mismo patrón se repetía en los otros dos tests de la clase.
Código bajo prueba
const loadRules = (source) => {
const loaded = {};
for (const name of COLLECTIONS) {
try {
loaded[name] = source.fetch(name).stream().toList();
} catch (error) {
log.warn('no se pudo cargar', name, error);
}
}
return loaded;
};Metele un bug
El código está como lo escribió quien lo programó: toda la suite pasa.
La suite
- pasacarga sin lanzar excepciónno tira
- pasamapea las tres colecciones configuradasloaded.a, b y c
- pasamapea las cuatro coleccionesObject.keys(loaded).length === 4
Las otras tres formas de estar en verde sin probar
| Síntoma | Qué está pasando | Cómo se detecta |
|---|---|---|
| Un doble sin configurar devuelve null | La rama no se ejecuta o se rompe en silencio | Ejecutar el test y leer los logs, no sólo el resultado |
| El test pasa por el motivo equivocado | El stub hace que la condición del nombre ni se evalúe | Romper el código a propósito: si sigue verde, no probaba eso |
| El test usa la fecha de hoy | Pasa hoy y falla el 31 de un mes, o en otra zona horaria | Inyectar el reloj; correr la suite con otra fecha |
| Todo el flujo en un solo caso | Cuando falla, no se sabe en qué paso | Separar en casos con nombre por comportamiento |
El segundo merece un párrafo. En un test llamado «no se muestra si ya se mostró hace poco», el doble que decidía esa condición estaba configurado para devolver siempre falso. El test pasaba —y hubiera pasado igual si alguien borraba entera la lógica de «hace poco»—. El nombre decía una cosa y la ejecución probaba otra.
Antes de seguir, predecí
Lo que sí funciona
Cinco hábitos que sostienen una suite honesta
- Escribir el test en rojo primero, aunque sea unos segundos. Un test que nunca estuvo en rojo no demostró que detecta nada.
- Mirar la salida de la corrida, no sólo el resultado. Un
ERRORen los logs de una suite verde es un test ciego esperando a que alguien lo lea. - Configurar todos los dobles que el código va a consultar, y que devuelvan colecciones
vacías en vez de
null: si el código sabe manejar una lista vacía, el test tiene que ejercitar ese camino, no el del puntero nulo. - Inyectar el reloj. Si la producción llama a la fecha actual y el test también, no están comparando lo mismo: están comparando dos lecturas distintas del mismo reloj, y el día que caigan a los lados de la medianoche la suite se pone roja sin que nadie haya tocado el código.
- Un caso por comportamiento, con nombre. Cuando el pipeline falla a las tres de la mañana, el nombre del caso es el primer dato del diagnóstico.
Más a fondo · nivel seniorPor qué la cobertura no lo detecta
La cobertura de líneas registra ejecución, no verificación. En el caso del principio, la línea del
.stream() se ejecuta —tira una excepción, pero se ejecuta—, el catch se ejecuta y ambas cuentan
como cubiertas. Una suite con 90 % de cobertura puede tener cero afirmaciones útiles. Las métricas
que sí correlacionan con detección son las que rompen el código a propósito y miden cuántos tests
se ponen en rojo.
Lo que preguntan sobre esto
Cierre
Autoevaluación
¿Lo entendiste?
Práctica