ConstructiCat Logo
CodeBust.
Browse section ▾

General Fixture.

Una configuración compartida construye un gran fixture «todo en uno» que cubre las necesidades de todas las pruebas, pero cada prueba individual ejercita solo una pequeña parte de él.

##Signs and Symptoms

Un único beforeEach/setUp (o una configuración compartida a nivel de módulo) construye montones de objetos, siembra muchos registros y conecta varios mocks, y cualquier prueba dada toca solo uno o dos de ellos. La heurística de detección del catálogo de test smells es simplemente: no todos los campos creados en la configuración son usados por todos los métodos de prueba.

Cómo reconocerlo:

  • El bloque de configuración es largo y crece cada vez que se añade una prueba nueva.
  • Para entender una sola prueba debes desplazarte hacia arriba y deducir por ingeniería inversa qué partes del fixture le importan realmente.
  • Muchas pruebas fallan o necesitan edición cuando cambias un objeto compartido que a la mayoría de ellas ni siquiera les importa.
  • El mismo fixture sirve a escenarios completamente distintos (creación, validación, pago, permisos) desde un único lugar.
// Olor: un fixture para todo; cada prueba usa solo una porción
let user, admin, product, cart, order, paymentGateway, shippingZone;

beforeEach(() => {
  user            = createUser({ name: 'Ada' });
  admin           = createAdmin();
  product         = createProduct({ price: 10 });
  cart            = createCart(user, [product]);
  order           = createOrder(cart);
  paymentGateway  = mockGateway();
  shippingZone    = createZone('EU');
});

test('cart subtotal sums its line items', () => {
  // solo necesita cart + product, pero paga por admin, order, gateway, zone…
  expect(cart.subtotal()).toBe(10);
});

##Reasons for the Problem

Por qué ocurre

  • DRY aplicado de forma demasiado agresiva. La configuración se consolida en un único hook compartido para "evitar la duplicación", de modo que las necesidades de cada nueva prueba se atornillan al mismo fixture.
  • Crecimiento orgánico. El fixture va acumulando objetos a medida que se añaden pruebas, y nadie poda lo que las pruebas más antiguas ya no usan.
  • Inercia de la configuración implícita. Una vez que existe un beforeEach grande, añadir una línea más es más fácil que crear un fixture enfocado para el nuevo caso.

Por qué duele

  • Legibilidad / Pruebas-como-Documentación. Una prueba debería leerse como una clara relación causa→efecto. Cuando la mayor parte del fixture es ruido irrelevante, el lector no puede saber qué impulsa realmente el resultado. El antídoto de Meszaros, el Minimal Fixture, existe precisamente porque una prueba que usa el fixture más pequeño siempre es más fácil de entender.
  • Mantenibilidad. Un cambio exigido por una prueba (p. ej., dar a product un nuevo campo obligatorio) obliga a editar una configuración compartida por todas las pruebas, creando roturas en cadena y acoplando entre sí pruebas no relacionadas.
  • Fiabilidad. El estado mutable y compartido del fixture permite que las pruebas se influyan entre sí y dificulta localizar los fallos: una fuente clásica de fragilidad dependiente del orden.
  • Velocidad. Cada prueba paga el coste completo de construir todo el fixture. General Fixture figura entre las causas de las Slow Tests.
  • Falsa confianza. Las pruebas acaban sobreespecificadas frente a detalles incidentales del fixture, de modo que pasan o fallan por razones ajenas al comportamiento bajo prueba.

##Treatment

Apunta a un Minimal Fixture: cada prueba configura solo lo que esa prueba necesita, y nada más.

  1. Inventaría el uso. Para cada campo compartido, enumera qué pruebas lo leen realmente. Cualquier cosa usada solo por un subconjunto es candidata a salir de la configuración compartida.
  2. Lleva la construcción a Creation Methods / builders de datos de prueba (Delegated Setup). Mantén las pruebas concisas sin forzar un único fixture gigante: cada prueba llama a un builder y sobrescribe solo los atributos relevantes para ella.
  3. Prefiere un Fresh Fixture por prueba en lugar de uno compartido de larga vida, eliminando el acoplamiento entre pruebas y el estado mutable compartido.
  4. Divide por escenario. Si grupos de pruebas comparten genuinamente un fixture pequeño, sepáralos en bloques describe enfocados (o clases de prueba), cada uno con su propia configuración mínima.
  5. Mantén en beforeEach solo lo que sea verdaderamente común y pequeño; mueve el resto en línea para que la causa y el efecto queden junto a la aserción.
// Antes: general fixture en un hook compartido (ver Signos y Síntomas)

// Después: configuración mínima y delegada: cada prueba construye solo lo que necesita
test('cart subtotal sums its line items', () => {
  const cart = aCart().withItem(aProduct().price(10)).build();
  expect(cart.subtotal()).toBe(10);
});

test('checkout charges the payment gateway', () => {
  const gateway = mockGateway();
  const cart    = aCart().withItem(aProduct().price(10)).build();

  checkout(cart, gateway);

  expect(gateway.charge).toHaveBeenCalledWith(10);
});

Ahora cada prueba se lee de arriba abajo como una historia autocontenida, los builders absorben el código repetitivo, y cambiar un escenario ya no perturba a los demás.