Prueba ansiosa.
Una Prueba ansiosa (Eager Test) verifica varios métodos o comportamientos distintos de la unidad bajo prueba en un solo método de prueba, en lugar de centrarse en un único comportamiento.
##Signs and Symptoms
Un único método de prueba ejercita muchos métodos de producción no relacionados y hace aserciones sobre cada uno, llevando al objeto a través de toda una secuencia de operaciones en lugar de comprobar un único resultado.
Señales delatoras:
- Un nombre de prueba vago y comodín (
testUserService,it('works')) o uno cosido con «y» (creates_and_renames_and_deletes). - Múltiples ciclos actuar → afirmar en un mismo cuerpo: llamas a un método, afirmas, llamas a otro, afirmas de nuevo.
- Comentarios usados como encabezados de sección dentro de la prueba (
// now test delete) para separar las cosas que se comprueban. - Las aserciones tocan varios métodos/campos distintos del SUT que no comparten un único resultado lógico.
// SMELL: una prueba verifica create, rename, delete y list
test('user service', () => {
const svc = new UserService();
const user = svc.create({ name: 'Ada' }); // comportamiento 1
expect(user.id).toBeDefined();
svc.rename(user.id, 'Grace'); // comportamiento 2
expect(svc.get(user.id).name).toBe('Grace');
svc.delete(user.id); // comportamiento 3
expect(svc.get(user.id)).toBeUndefined();
expect(svc.list()).toHaveLength(0); // comportamiento 4
});
Fíjate en la distinción: una Prueba ansiosa trata de probar varios comportamientos, no simplemente de tener varios expect. Varias aserciones sobre un único resultado lógico (por ejemplo, comprobar varios campos del mismo objeto devuelto) está bien y no es este olor.
##Reasons for the Problem
Por qué ocurre
- Reutilización del setup / pereza. Preparar el fixture es tedioso o costoso, así que resulta tentador seguir pinchando el objeto ya construido y afirmar «ya que estamos».
- Pensamiento de flujo de trabajo. El autor prueba un recorrido de usuario («crear, luego editar, luego eliminar») como un solo guion en lugar de aislar cada comportamiento unitario.
- Deriva del TDD. Una prueba que empezó enfocada va acumulando aserciones extra a medida que se añade nueva funcionalidad, en vez de darle su propia prueba.
Por qué duele
- Falsa confianza (la principal). La mayoría de las bibliotecas de aserción abortan la prueba en la primera aserción que falla. Si
createse rompe, las comprobaciones derename,deleteylistnunca se ejecutan, de modo que un cambio de verde a rojo oculta cuántos comportamientos están realmente rotos, y una prueba que pasaba en realidad nunca estaba ejercitando los pasos posteriores una vez que uno anterior sufrió una regresión. - Diagnóstico pobre. Un fallo te dice «la prueba del servicio de usuario falló», no qué comportamiento. Tienes que leer todo el método para localizar el paso roto.
- Intención difusa / legibilidad. La prueba ya no documenta un único hecho sobre el sistema; quien la lee debe segmentarla mentalmente en los comportamientos que agrupa.
- Fragilidad y acoplamiento. Las aserciones posteriores dependen de mutaciones anteriores, de modo que un cambio no relacionado cerca del principio se propaga en cascada y hace que la prueba sea frágil y difícil de refactorizar.
- Mantenibilidad. Es más difícil eliminar, mover o renombrar la cobertura de un comportamiento cuando está enredada con otros tres en un mismo método.
##Treatment
Divide la prueba ansiosa en varias pruebas enfocadas —un comportamiento por prueba— y eleva el paso de preparación compartido al setup para que la división no duplique código repetitivo.
Pasos:
- Enumera los comportamientos que la prueba agrupa (aquí: create, rename, delete, list tras delete).
- Extrae una prueba por comportamiento, cada una con un nombre descriptivo que revele su intención.
- Mueve el setup compartido a un
beforeEacho a una factoría/Método de creación (Creation Method) para que cada prueba siga teniendo un SUT limpio sin código de preparación copiado y pegado. - Mantén un único Act por prueba y haz aserciones únicamente sobre el resultado de ese comportamiento (varios
expectsobre el mismo resultado están bien). - Si realmente necesitas validar un flujo de trabajo de extremo a extremo, consérvalo como un único escenario/prueba de integración con nombre explícito, pero aun así cubre cada comportamiento unitario en su propia prueba en lugar de depender del flujo para la cobertura.
- Protégete frente a regresiones activando una regla de máximo de aserciones (consulta los detectores) como aproximación barata.
// AFTER: pruebas enfocadas, setup compartido
let svc;
beforeEach(() => { svc = new UserService(); });
test('create() assigns an id', () => {
expect(svc.create({ name: 'Ada' }).id).toBeDefined();
});
test('rename() updates the stored name', () => {
const { id } = svc.create({ name: 'Ada' });
svc.rename(id, 'Grace');
expect(svc.get(id).name).toBe('Grace');
});
test('delete() removes the user', () => {
const { id } = svc.create({ name: 'Ada' });
svc.delete(id);
expect(svc.get(id)).toBeUndefined();
});
Ahora cualquier comportamiento puede fallar de forma independiente, el nombre de la prueba que falla señala con precisión la rotura, y cada prueba se lee como un hecho documentado sobre el SUT.
##Detected by
- eslint-jest jest/max-expects — Imponer un número máximo de llamadas a expect() por prueba
- eslint-vitest vitest/max-expects — Imponer un número máximo de expect por prueba