---
title: "Código difícil de probar"
type: "test-smell"
slug: "hard-to-test-code"
url: "http://localhost:3000/es/test-smells/hard-to-test-code.md"
category: "Olores de acoplamiento"
description: "Código de producción cuyo diseño (acoplamiento fuerte, dependencias ocultas, estado global, E-S no determinista o interfaces solo asíncronas) obliga a los tests a contorsiones incómodas, o hace imposible ejercitar una unidad en aislamiento."
---
# Código difícil de probar

> Código de producción cuyo diseño (acoplamiento fuerte, dependencias ocultas, estado global, E-S no determinista o interfaces solo asíncronas) obliga a los tests a contorsiones incómodas, o hace imposible ejercitar una unidad en aislamiento.

## Signs and Symptoms

Este olor vive en el código de _producción_, pero lo descubres al escribir tests. La señal reveladora es que un test unitario no se puede escribir con limpieza: el test tiene que pelear con el diseño para poner el código bajo prueba en un estado conocido y observar el resultado.

Vigila estos síntomas:

* **Bloques de preparación gigantes.** Debes construir una maraña de colaboradores solo para instanciar la clase bajo prueba ("no puedo probar `OrderService` sin cablear también una BD, una pasarela y un cargador de configuración").
* **Sin costura para inyectar un doble.** El código llama a `new ConcreteThing()`, accede a un singleton estático (`Database.getInstance()`) o golpea la red/el sistema de archivos/el reloj directamente, así que no hay dónde sustituir un stub o un fake.
* **No determinismo incrustado.** La lógica depende de `Date.now()`, `Math.random()`, `process.env` o de la temporización del reloj que el test no puede controlar, produciendo resultados inestables o irrepetibles.
* **Sin punto de observación / sin punto de control.** El resultado que quieres afirmar está enterrado en estado privado o en un efecto secundario, así que los tests hurgan mediante reflexión, conversiones o getters solo para pruebas.
* **Interfaz solo asíncrona.** La única forma de impulsar el código es arrancar un temporizador/hilo/cola y luego `sleep()` o sondear, porque la finalización nunca es observable directamente.
* **Cambias el código de producción solo para probarlo.** Aflojar `private` a `public`, añadir ganchos de prueba `if (testMode) { ... }` o crear subclases solo para alcanzar las entrañas.

```js
// Difícil de probar: dependencias ocultas + cableadas, estado global, no determinismo
class OrderService {
  placeOrder(cart) {
    const db = Database.getInstance();            // singleton global (sin costura)
    const gateway = new StripeGateway(API_KEY);   // dep concreta cableada -> red real
    const id = Math.random().toString(36).slice(2); // no determinista
    const charge = gateway.charge(cart.total);    // llamada HTTP real dentro de la unidad
    db.save({ id, at: Date.now(), charge });       // reloj no inyectado
    return id;
  }
}
// Para hacer un "test unitario" de esto debes golpear Stripe, parchear Date/Math globalmente
// e inspeccionar un singleton compartido — es decir, Pruebas indirectas a través de interfaces incómodas.

```

## Reasons for the Problem

**Por qué ocurre**

* **Test-Last sobre código heredado.** Meszaros nombra la causa raíz como la _falta de diseño para la testabilidad_. La testabilidad surge de forma natural del TDD, pero cuando los tests se escriben "al final", nada presionó al diseño para que expusiera puntos de control y de observación, así que hay que añadirlos a posteriori.
* **Acoplamiento fuerte y dependencias cableadas.** Instanciar colaboradores concretos con `new` (o sacarlos de singletons estáticos) significa que no puedes sustituirlos por dobles de prueba: el test ejercita la unidad _y_ todo lo que toca.
* **Estado global/compartido y entradas ocultas.** Los singletons, las variables estáticas, el tiempo ambiente, la aleatoriedad y las lecturas del entorno son entradas que el test nunca declaró y no puede fijar, así que el comportamiento es implícito e incontrolable.
* **Asincronía y efectos secundarios en el núcleo.** Cuando la lógica de negocio se entrelaza con hilos, colas, E-S o la interfaz de usuario, no hay ningún valor puro que afirmar.

**Por qué hace daño**

* **Fiabilidad.** Los tests obligados a usar E-S reales, sleeps, singletons compartidos o relojes globales se vuelven lentos, inestables y dependientes del orden: el camino clásico hacia un Test errático/frágil.
* **Mantenibilidad.** Las configuraciones enormes y los tests que hurgan en las entrañas acoplan la suite de tests a los detalles de implementación, de modo que refactorizaciones inofensivas rompen tests (Test frágil / Fixture frágil).
* **Falsa confianza.** Las vías de código más difíciles e importantes se prueban solo a través de interfaces toscas e indirectas, o se saltan por completo. Los números de cobertura parecen bien mientras la lógica arriesgada apenas se ejercita.
* **Legibilidad.** Un test dominado por el andamiaje oscurece el único comportamiento que se supone que debe especificar, así que no documenta nada.

En el Catálogo abierto de olores de test, el _Código difícil de probar_ es la contraparte, en el lado de producción, de olores de test como las _Pruebas indirectas_ y el _Test frágil_: el mal diseño es la causa, los tests incómodos son el síntoma.

## Treatment

Arregla el _diseño_, no el test. El objetivo es dar a cada unidad un **punto de control** (una forma de ponerla en un estado conocido) y un **punto de observación** (una forma de leer el resultado).

1. **Introduce costuras mediante inyección de dependencias.** Pasa los colaboradores (inyección por constructor) en lugar de construirlos o buscarlos. Inyecta también las entradas ambiente —reloj, generador de id/uuid, fuente de aleatoriedad— para que se conviertan en parámetros controlables.
2. **Depende de abstracciones.** Programa contra una interfaz y sustituye un stub/fake/mock en los tests. Esto elimina directamente la _Dependencia cableada_ de Designite y recorta la _Dependencia excesiva_.
3. **Aplica el patrón Humble Object.** Empuja las partes genuinamente difíciles de probar (UI, pegamento asíncrono, E-S en crudo) a un adaptador fino sin lógica, y mueve la lógica de decisión a un objeto plano y síncrono que puedas probar directamente.
4. **Extrae funciones puras.** Separa el cálculo de los efectos secundarios; afirma sobre los valores devueltos, realiza la E-S solo en el límite.
5. **Haz observable lo asíncrono.** Devuelve una promesa/futuro o expón una señal de finalización, e inyecta el planificador para que los tests usen temporizadores falsos en vez de `sleep`.
6. **Para el código heredado que aún no puedes rediseñar,** usa una _Subclase específica de prueba_ o una costura de alcance reducido (técnicas de Feathers) para poner el código bajo prueba, y luego refactoriza hacia la inyección, en lugar de dejar ganchos `if (testMode)` permanentes en producción.
7. **Evita el estado global/estático.** Reemplaza los singletons por dependencias pasadas explícitamente para que los tests no compartan ni reinicien estado oculto.

```js
// DESPUÉS: dependencias, reloj e id inyectados -> probable en aislamiento
class OrderService {
  constructor(db, gateway, clock = () => Date.now(), newId = uuid) {
    this.db = db; this.gateway = gateway; this.clock = clock; this.newId = newId;
  }
  placeOrder(cart) {
    const id = this.newId();
    const charge = this.gateway.charge(cart.total); // una abstracción PaymentGateway
    this.db.save({ id, at: this.clock(), charge });
    return id;
  }
}

// Test: determinista, sin red, sin parcheo global, rápido.
const svc = new OrderService(fakeDb, fakeGateway, () => 1000, () => 'id-1');
expect(svc.placeOrder({ total: 50 })).toBe('id-1');
expect(fakeGateway.charge).toHaveBeenCalledWith(50);
expect(fakeDb.saved[0]).toEqual({ id: 'id-1', at: 1000, charge: fakeGateway.result });

```

## Detected by

- **designite** `Hard-wired Dependency (testability smell)` — Dependencia cableada (https://www.designite-tools.com/blog/understanding-testability-test-smells)
- **designite** `Excessive Dependency (testability smell)` — Dependencia excesiva (https://www.designite-tools.com/blog/understanding-testability-test-smells)
- **designite** `Global State (testability smell)` — Estado global (https://www.designite-tools.com/blog/understanding-testability-test-smells)
- **designite** `Law of Demeter Violation (testability smell)` — Violación de la Ley de Deméter (https://www.designite-tools.com/blog/understanding-testability-test-smells)
