ConstructiCat Logo
CodeBust.
Browse section ▾

Prueba verbosa.

Una prueba que usa mucho más código del necesario para plantear su escenario, sepultando la única relación de causa y efecto que verifica bajo código repetitivo de preparación, datos irrelevantes y aserciones campo por campo.

##Signs and Symptoms

No puedes saber de un vistazo qué demuestra una prueba, aunque no esté ocurriendo nada complicado: la intención queda ahogada en el volumen. Indicios habituales:

  • El método de prueba es largo (el catálogo de Test Smells usa un umbral aproximado de >30 líneas) o se extiende más allá de una pantalla.
  • Un gran bloque arrange construye objetos campo por campo, gran parte de ellos irrelevantes para el comportamiento bajo prueba.
  • Muchos literales codificados a mano sin un vínculo evidente entre las entradas y las salidas afirmadas.
  • El resultado se comprueba con un montón de aserciones de un solo campo en lugar de una única comparación significativa.
  • Al leer las aserciones, no resulta fácil relacionar qué entrada provocó qué valor esperado.
test('places an order for a returning customer', () => {
  const address = new Address();
  address.street = '123 Main St';
  address.city = 'Springfield';
  address.zip = '00000';                          // irrelevante para esta prueba
  address.country = 'US';

  const customer = new Customer();
  customer.id = 42;
  customer.firstName = 'Ada';
  customer.lastName = 'Lovelace';
  customer.email = 'ada@example.com';
  customer.address = address;
  customer.createdAt = new Date('2020-01-01');     // ruido

  const product = new Product();
  product.sku = 'SKU-1';
  product.name = 'Widget';
  product.price = 9.99;

  const cart = new Cart();
  cart.add(product, 2);

  const order = orderService.placeOrder(customer, cart);

  expect(order.status).toBe('CONFIRMED');          // las únicas líneas que importan
  expect(order.lineItems.length).toBe(1);
  expect(order.lineItems[0].sku).toBe('SKU-1');
  expect(order.lineItems[0].quantity).toBe(2);
  expect(order.total).toBe(19.98);
  expect(order.customerId).toBe(42);
});

Alrededor de 25 líneas de construcción custodian una aserción de dos líneas sobre totales y estado.

##Reasons for the Problem

Por qué ocurre

  • Construcción en línea con campos obligatorios. Construir objetos reales mediante constructores/setters te obliga a aportar datos que a la prueba no le importan, solo para que el código compile y se ejecute. Meszaros señala que el nivel de detalle necesario para hacer ejecutable una prueba puede volverla «tan verbosa que resulta difícil de entender».
  • Crecimiento por copiar y pegar. Una prueba empieza siendo pequeña y luego cada nuevo escenario se crea copiando el anterior y ajustando un valor, así que el ruido se acumula.
  • Sin ayudantes de construcción compartidos. Sin métodos de creación (Creation Methods), constructores (builders) ni fixtures, cada prueba vuelve a declarar el grafo de objetos completo.
  • Datos codificados a mano y verificación campo por campo. Escribir literales por todas partes y hacer aserciones sobre cada propiedad parece «exhaustivo», pero multiplica el número de líneas.

Por qué resulta perjudicial

  • Legibilidad. La Prueba verbosa es una variante de la Prueba oscura: el catálogo incluso indica «Prueba oscura» como su alias. Quien lee no puede ver la relación de causa y efecto entre el fixture y el resultado, que es el sentido mismo de una prueba basada en ejemplos.
  • Mantenibilidad. Una prueba de más de 30 líneas tiende a cargar con «varias responsabilidades», de modo que un pequeño cambio en producción se propaga a muchas líneas a lo largo de muchas pruebas. La preparación en línea masiva también genera duplicación.
  • Falsa confianza. Las pruebas largas y ruidosas ocultan errores a plena vista: un literal incorrecto o una aserción mal colocada pasan fácilmente desapercibidos, y la verbosidad hace que los revisores lean por encima. El volumen aparenta rigor sin demostrar nada más.
  • Fiabilidad. Cuanto más estado irrelevante fija una prueba, más probable es que se rompa por motivos ajenos a su intención (sobreespecificación), produciendo fallos frágiles y de baja señal.

##Treatment

Saca el ruido fuera del cuerpo de la prueba para que solo queden visibles las variables que determinan el comportamiento. Acciones concretas (todas de xUnit Test Patterns):

  1. Extrae métodos de creación (Creation Methods) / usa un constructor (builder). Sustituye la construcción de objetos en línea por un ayudante con un nombre evocador que aporte valores por defecto razonables; pásale solo los valores que de verdad le importan a la prueba (método de creación parametrizado). Esto elimina tanto el código repetitivo como la información irrelevante.
  2. Asigna por defecto los datos irrelevantes dentro del ayudante, señalando que «estos valores no afectan al resultado».
  3. Sustituye las comprobaciones campo por campo por un objeto esperado (Expected Object) (toEqual/toMatchObject) o una aserción personalizada / método de verificación que nombre el concepto que se está verificando.
  4. Verifica un solo comportamiento por prueba. Si una sola prueba es larga porque ejercita varios resultados (una Prueba ansiosa, Eager Test), divídela.
  5. Parametriza las pruebas verbosas casi idénticas con test.each / it.each en lugar de copiar y pegar.
// antes  — véase Indicios y síntomas (≈30 líneas de preparación + 6 aserciones)

// después
test('confirms the order and charges line-item total', () => {
  const customer = aCustomer();                          // Método de creación, valores por defecto
  const cart = aCartWith(aProduct({ price: 9.99 }), 2);  // solo la entrada relevante

  const order = orderService.placeOrder(customer, cart);

  expect(order).toMatchObject({                          // Objeto esperado, no campo por campo
    status: 'CONFIRMED',
    total: 19.98,
  });
});

El escenario ahora se lee en segundos: un cliente compra dos unidades de un producto de 9,99 $ → el pedido se confirma con un total de 19,98 $. Los ayudantes (aCustomer, aProduct, aCartWith) son utilidades compartidas y probadas, así que el ruido vive en un solo lugar en lugar de en cada prueba.

Salvaguarda: limita en tu linter la longitud de las funciones de prueba y el número de aserciones (véanse los detectores) para que las pruebas no vuelvan a crecer en silencio hasta convertirse en Pruebas verbosas.

##Detected by

  • eslint max-lines-per-functionImpone un número máximo de líneas por función: señala los cuerpos de prueba que superan el límite configurado, la medida central de una Prueba verbosa.
  • eslint max-statementsImpone un número máximo de sentencias en un bloque de función: limita cuánto puede hacer una única prueba (un callback de función o de flecha).
  • sonar S138Las funciones no deberían tener demasiadas líneas de código: se aplica a las funciones de prueba; señala los métodos de prueba sobredimensionados y con múltiples responsabilidades (RSPEC-138).
  • eslint-jest jest/max-expectsImpone un número máximo de llamadas a expect() por prueba (5 por defecto): detecta la faceta de acumulación de aserciones de las pruebas verbosas/ansiosas.
  • eslint-vitest vitest/max-expectsImpone un número máximo de llamadas a expect() por prueba (5 por defecto): equivalente de Vitest a jest/max-expects.