---
title: "Solo para los tests"
type: "test-smell"
slug: "for-testers-only"
url: "http://localhost:3000/es/test-smells/for-testers-only.md"
category: "Olores de acoplamiento"
description: "El código de producción contiene métodos, accesores de estado o costuras (seams) que existen únicamente para que los usen los tests, contaminando la API real e invitando a tests que verifican detalles internos en lugar de comportamiento."
---
# Solo para los tests

> El código de producción contiene métodos, accesores de estado o costuras (seams) que existen únicamente para que los usen los tests, contaminando la API real e invitando a tests que verifican detalles internos en lugar de comportamiento.

## Signs and Symptoms

Encuentras miembros en el código de **producción** cuyos únicos llamadores viven en archivos de test. Señales reveladoras:

* Métodos, getters/setters, exports o parámetros de constructor usados exclusivamente desde `*.test.ts` / `*.spec.ts`.
* Nombres o marcadores con aroma a test: `getStateForTest`, `resetForTesting`, `__getInternal`, `FTO_*`, `forTest`, comentarios como `// only used by tests`, o anotaciones como `@VisibleForTesting` / `@TestOnly` / `@internal`.
* Visibilidad relajada (un campo vuelto `public`/exportado, un `#private` convertido en `protected`) únicamente para que un test pueda alcanzar el estado interno.
* «Costuras» (seams) adicionales —un `setClock(...)`, `setRandom(...)` o `reset()`— añadidas solo porque un test las necesitaba, no porque el diseño real las requiera.

```ts
// payment-service.ts  (código de PRODUCCIÓN)
export class PaymentService {
  #ledger: Entry[] = [];

  charge(amount: number) { /* ... */ }

  // Nada en producción llama nunca a estos; solo lo hacen los tests:
  getLedgerForTest() { return this.#ledger; }          // expone detalles internos
  setClockForTest(now: () => Date) { this.now = now; } // costura solo para tests
  FTO_reset() { this.#ledger = []; }                   // "For Tests Only"
}

```

Una comprobación rápida: un `grep -rn 'forTest\|ForTesting\|FTO_' src/` que devuelva coincidencias en el código fuente de producción, o una pasada de código muerto (p. ej. Knip en modo producción) que informe de un export como no usado mientras un test claramente lo importa.

## Reasons for the Problem

**Por qué ocurre**

* _Adaptar tests a código no testeable._ Cuando el código heredado no se diseñó para ser testeable, la forma más rápida de hacer una aserción sobre un resultado es abrir un agujero en el SUT y leer sus detalles internos; Meszaros lo señala como la causa principal de _For Tests Only_.
* _APIs asimétricas._ Los clientes reales usan un objeto de una manera (escritura); los tests lo usan de forma simétrica (escriben **y luego** leen de vuelta para verificar), así que los tests «necesitan» accesores que ningún llamador de producción necesita.
* _Presión de plazos._ Añadir una puerta trasera es más barato en el momento que refactorizar hacia un diseño en el que el comportamiento sea observable a través del contrato público.

**Por qué es perjudicial**

* _Legibilidad._ La API pública deja de decir la verdad: los responsables del mantenimiento no pueden distinguir el contrato real del andamiaje de los tests, y cada lector tiene que preguntarse «¿se usa de verdad este método?».
* _Encapsulación y fiabilidad._ El estado interno se vuelve accesible y mutable en el código que se distribuye. Una llamada perdida a `FTO_reset()` o un setter filtrado pueden corromper el estado en producción; la superficie adicional también infla el bundle y amplía la superficie de ataque.
* _Falsa confianza._ Los tests que hurgan en el estado privado hacen aserciones sobre la _implementación_, no sobre el _comportamiento_. Pueden seguir en verde mientras el contrato público está roto, y se rompen ante refactorizaciones inofensivas: tests frágiles que prueban lo que no deben.
* _Mantenibilidad._ Los miembros solo para tests parecen código muerto pero no pueden eliminarse; cada cambio tiene que tener en cuenta llamadores fantasma, y el mal olor tiende a multiplicarse a medida que más tests reutilizan la puerta trasera.

## Treatment

Trata la puerta trasera como una señal de diseño, no como un detalle del fixture.

1. **Prueba primero a través del comportamiento observable.** Haz aserciones sobre valores de retorno, eventos emitidos, salida persistida o interacciones con colaboradores (mediante dobles de test) en lugar de hurgar en los detalles internos. La mayoría de los accesores `getXForTest` desaparecen en cuanto verificas lo que el objeto _hace_, no lo que _contiene_.
2. **Usa una Test-Specific Subclass** cuando realmente necesites acceso interno. Extiende la clase en el test para exponer un miembro `protected`, en lugar de ampliar la visibilidad en producción.
3. **Haz que las costuras formen parte del diseño real, no de escotillas solo para tests.** Inyectar un reloj o un RNG a través del constructor normal es una inyección de dependencias legítima; un mutador `setClockForTest()` es un mal olor. Si una costura solo tiene sentido para los tests, lleva el comportamiento a un **Strategy/Null Object** que producción instale por defecto y el test sustituya.
4. **Si la exposición es realmente inevitable, etiquétala a gritos y vállala.** Márcala con `@VisibleForTesting` / `@internal` (o una convención de nombres `FTO_`) y haz cumplir que el código de producción nunca la llame (véanse los detectores). Una costura marcada y protegida es mejor que una silenciosa.
5. **Caza a los infractores existentes.** Haz `grep` de `forTest`/`ForTesting`/`FTO_` y ejecuta una herramienta de uso/código muerto como Knip en modo producción para sacar a la luz los exports referenciados únicamente por archivos de test.

```ts
// ANTES — producción arrastra un accesor solo para tests
export class Cart {
  #items: Item[] = [];
  add(i: Item) { this.#items.push(i); }
  getItemsForTest() { return this.#items; } // solo para los testers
}
// test
expect(cart.getItemsForTest()).toHaveLength(1);

```

```ts
// DESPUÉS — verifica el comportamiento a través del contrato real
export class Cart {
  #items: Item[] = [];
  add(i: Item) { this.#items.push(i); }
  get count() { return this.#items.length; }
  get total() { return this.#items.reduce((s, i) => s + i.price, 0); }
}
// test
cart.add({ price: 10 });
expect(cart.count).toBe(1);
expect(cart.total).toBe(10);

```

Si aún necesitas acceso interno, restringe la costura a los tests con una subclase en lugar de abrirla al mundo entero:

```ts
// la producción se mantiene limpia: queue es protected, no public
export class Scheduler {
  protected queue: Job[] = [];
  enqueue(j: Job) { this.queue.push(j); }
}
// solo en el archivo de test
class TestScheduler extends Scheduler {
  peek() { return this.queue; }
}

```

## Detected by

- **sonar** `java:S5803` — Los miembros «@VisibleForTesting» no deben usarse desde el código de producción (https://rules.sonarsource.com/java/RSPEC-5803/)
