ConstructiCat Logo
CodeBust.
Browse section ▾

Lógica de Prueba en Producción.

El código de producción contiene lógica, ramas o miembros que existen únicamente para dar soporte a las pruebas, difuminando la línea entre lo que se entrega y lo que solo se prueba.

##Signs and Symptoms

Encuentras código en el sistema bajo prueba (SUT) que solo importa cuando se ejecuta una prueba. Señales reveladoras:

  • Ganchos de prueba / banderas de modo: ramas condicionadas por una bandera testing, isTest, NODE_ENV === 'test' o mock que cortocircuitan el comportamiento real.
  • Miembros "solo para pruebas": setters públicos, getters, reset() o constructores añadidos únicamente para que una prueba pueda alcanzar el estado interno, a menudo etiquetados con @VisibleForTesting / @TestOnly y luego en realidad llamados desde producción.
  • Contaminación de igualdad: lógica equals() / de comparación añadida a una clase de producción únicamente para que una aserción pueda comparar dos objetos.
  • Dependencia de prueba en producción: módulos de producción que importan un framework de pruebas, un fixture o una fábrica de mocks.
// OLOR: el código entregado se comporta de forma distinta cuando "testing" está activo
class PaymentService {
  charge(order: Order) {
    if (process.env.NODE_ENV === 'test' || this.isTesting) {
      return { status: 'ok', id: 'FAKE-TEST-ID' }; // la pasarela real nunca se ejercita
    }
    return this.gateway.charge(order); // <-- la ruta que realmente se entrega
  }
}

La rama "real" es la que tus clientes utilizan, y es exactamente la rama que tus pruebas omiten.

##Reasons for the Problem

Por qué ocurre

  • El SUT es difícil de probar (se comunica con una red, un reloj, una pasarela de pago o el sistema de archivos) y añadir un atajo if (testing) es más rápido que introducir una costura adecuada.
  • Una prueba necesita observar o establecer un estado interno, así que un desarrollador lo expone "solo para la prueba".
  • Crear stubs/mocks resulta incómodo, así que se codifican datos predefinidos detrás de una bandera.

Por qué es perjudicial

  • Falsa confianza. Las pruebas ejercitan la rama exclusiva de prueba, por lo que la ruta de producción se entrega sin probar. Las pruebas verdes demuestran que el simulacro funciona, no la realidad.
  • Fiabilidad / seguridad. El código exclusivo de prueba que sobrevive hasta producción puede ejecutarse en condiciones reales. La advertencia canónica de Meszaros es el Ariane 5: un código destinado solo a tierra que quedó activo en vuelo provocó el fallo. Un if (isTesting) dejado habilitado es la versión software de eso.
  • Seguridad. Los atajos de prueba son puertas traseras: una bandera que omite la autenticación, el pago o la validación está a un error de configuración de ser explotable.
  • Legibilidad e hinchazón de la API. Los setters/getters/reset() exclusivos de prueba agrandan la superficie pública e inducen a error a los clientes reales sobre para qué sirve la clase.
  • Mantenibilidad. Dos comportamientos conviven en una misma clase; cada cambio debe razonar tanto sobre la ruta de producción como sobre la de prueba, y la divergencia se va pudriendo en silencio.

##Treatment

Saca la lógica de prueba del código de producción introduciendo una costura adecuada en lugar de una bandera.

  1. Inyecta la variación (Inyección de Dependencias + Doble de Prueba). Reemplaza la rama codificada de forma fija por un colaborador que la prueba sustituye. En producción se conecta la implementación real; en la prueba se conecta un fake/stub/mock.
  2. Usa una Subclase Específica de Prueba cuando solo necesites sobrescribir un método: sobrescríbelo en una subclase que viva en el código de prueba, no mediante un if en la clase base.
  3. Aplica el patrón Humble Object para llevar la lógica difícil de probar (reloj, E/S) detrás de un adaptador fino, de modo que la lógica central se vuelva directamente comprobable sin ganchos.
  4. Mueve la lógica de comparación al lado de la prueba. En lugar de contaminar producción con equals() para las aserciones, usa un matcher/comparador personalizado o afirma sobre los campos que te interesan.
  5. Mantén el código de prueba fuera de la compilación. Usa separación de source-sets / configuración de compilación para que los ayudantes de prueba no puedan compilarse en el artefacto entregado; marca los miembros que de verdad deban ser visibles para las pruebas con @VisibleForTesting/@TestOnly y deja que un linter haga cumplir que producción nunca los llame.
// ANTES: gancho de prueba dentro del código de producción
class PaymentService {
  charge(order: Order) {
    if (this.isTesting) return { status: 'ok', id: 'FAKE-TEST-ID' };
    return this.gateway.charge(order);
  }
}

// DESPUÉS: una única ruta de código; la pasarela se inyecta y se simula en la prueba
class PaymentService {
  constructor(private gateway: PaymentGateway) {}
  charge(order: Order) {
    return this.gateway.charge(order); // la misma ruta en producción y en prueba
  }
}

// prueba
const fakeGateway = { charge: () => ({ status: 'ok', id: 'FAKE-TEST-ID' }) };
const service = new PaymentService(fakeGateway);

Ahora la ruta de producción es la única ruta, y la prueba controla el comportamiento desde fuera.

##Detected by