Optimismo de Recursos.
El Optimismo de Recursos es cuando una prueba asume que un recurso externo (un archivo, directorio, tabla de base de datos, variable de entorno o endpoint de red) ya existe y está en un estado conocido, en lugar de aprovisionarlo y verificarlo, haciendo que la prueba pase o falle de forma no determinista.
##Signs and Symptoms
Una prueba toca algo fuera de sí misma —un archivo, una ruta temporal, un directorio, una fila de base de datos, una variable de entorno o un endpoint remoto— y simplemente confía en que ya está ahí y con la forma correcta. No hay ningún paso de preparación que cree el recurso ni ninguna comprobación de que existe antes de usarlo.
Señales reveladoras:
- Se lee o escribe una ruta codificada de forma fija sin un
existsSync/mkdir/writeFileprevio: p. ej.fs.readFileSync('/tmp/app/config.json'). - La prueba pasa en tu máquina o en la primera ejecución, y luego falla en un checkout limpio, en CI, en un directorio temporal de otro SO, o cuando la suite se ejecuta en otro orden o en paralelo.
- El recurso que necesita en realidad lo crea otra prueba (o un paso manual/solo de desarrollo), así que la prueba solo funciona como efecto secundario del orden de ejecución.
- Abre un archivo/conexión y afirma sobre el resultado sin afirmar nunca que el recurso estaba presente: un recurso ausente lanza una excepción antes de la aserción real, o produce silenciosamente datos vacíos que aun así "pasan".
test('parses the config file', () => {
// optimista: asume que /tmp/app/config.json ya existe y está bien formado
const raw = fs.readFileSync('/tmp/app/config.json', 'utf8');
expect(JSON.parse(raw).port).toBe(8080);
});
La heurística de detección canónica (testsmells.org / tsDetect): una prueba usa un recurso de tipo File sin llamar primero a una comprobación de existencia/validez como exists(), isFile() o notExists().
##Reasons for the Problem
Por qué ocurre
- El recurso estaba presente cuando se escribió la prueba (un archivo de fixture en el repositorio, una base de datos de desarrollo con datos sembrados, un archivo temporal dejado por un paso anterior), así que el autor nunca percibe su ausencia.
- Sencillamente es menos código apuntar a una ruta o tabla existente que asignar y sembrar una en la preparación, y desmontarla después.
- Copiar y pegar de otra prueba que ya dependía de un estado ambiental compartido.
Por qué es perjudicial
- No determinismo / inestabilidad. El resultado depende del estado del entorno, no del código bajo prueba. van Deursen et al. describen exactamente esto: pruebas que "funcionan bien en un momento y fallan estrepitosamente en otro". Los ejecutores de CI limpios, los checkouts recién hechos, los workers en paralelo y las distintas ubicaciones temporales del SO son donde se nota.
- Falsa confianza. Una prueba verde puede estar pasando por un estado residual de una ejecución anterior o de una prueba previa, no porque el código actual sea correcto, o bien un recurso ausente lanza una excepción pronto y la aserción significativa nunca se ejecuta.
- Acoplamiento oculto y dependencia del orden. Cuando una prueba crea lo que otra consume, la suite tiene un contrato de orden invisible que se rompe al barajar o fragmentar (sharding).
- Difícil de reproducir y mantener. Los fallos no pueden reproducirse localmente porque dependen de un estado ambiental específico de la máquina, así que la depuración es lenta y la prueba erosiona la confianza.
##Treatment
Haz que cada prueba posea y controle cada recurso que toca, y nunca asuma un estado ambiental.
- Aprovisiona en la preparación, limpia en el desmontaje. Usa Setup External Resource: asigna e inicializa archivos, directorios, tablas de BD y conexiones en
beforeEach/beforeAll, y libéralos enafterEach/afterAllpara que la siguiente ejecución empiece limpia. - Crea, no asumas. Escribe el archivo, siembra la tabla o arranca el servidor stub en la preparación. Si realmente debes consumir un recurso preexistente, afirma primero que existe para que un recurso ausente falle de forma evidente con un mensaje claro en lugar de corromper la aserción real.
- Aísla por prueba. Usa un directorio temporal único (
fs.mkdtemp(os.tmpdir() + …)) o un esquema/espacio de nombres nuevo por prueba en lugar de una ruta compartida codificada de forma fija, para que las ejecuciones en paralelo y las repeticiones no colisionen. - Mejor aún, elimina la dependencia. Reemplaza el recurso real por un mock o un fake en memoria (
fssimulado, BD en memoria, HTTP con stub) para que la prueba sea totalmente autónoma y determinista, que es la solución que recomienda testsmells.org.
Antes → después:
// antes — Optimismo de Recursos
test('parses the config file', () => {
const raw = fs.readFileSync('/tmp/app/config.json', 'utf8');
expect(JSON.parse(raw).port).toBe(8080);
});
// después — la prueba posee y verifica su recurso
import { mkdtemp, writeFile, rm, readFile } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
let dir;
beforeEach(async () => {
dir = await mkdtemp(path.join(os.tmpdir(), 'cfg-'));
await writeFile(path.join(dir, 'config.json'), JSON.stringify({ port: 8080 }));
});
afterEach(() => rm(dir, { recursive: true, force: true }));
test('parses the config file', async () => {
const raw = await readFile(path.join(dir, 'config.json'), 'utf8');
expect(JSON.parse(raw).port).toBe(8080);
});
##Detected by
- tsDetect Resource Optimism — Optimismo de Recursos