---
title: "Test Run War"
type: "test-smell"
slug: "test-run-war"
url: "http://localhost:3000/es/test-smells/test-run-war.md"
category: "Olores erráticos"
description: "Un Test Run War ocurre cuando las pruebas pasan para una persona pero fallan de forma aleatoria en el momento en que varias personas o trabajos de CI ejecutan la suite al mismo tiempo, porque las pruebas chocan sobre un fixture compartido y persistente."
---
# Test Run War

> Un Test Run War ocurre cuando las pruebas pasan para una persona pero fallan de forma aleatoria en el momento en que varias personas o trabajos de CI ejecutan la suite al mismo tiempo, porque las pruebas chocan sobre un fixture compartido y persistente.

## Signs and Symptoms

Reconoces un Test Run War por _quién_ y _cuándo_, no por el cuerpo de la prueba en sí:

* La suite está en verde en tu máquina y en ejecuciones de CI en solitario, pero se pone en rojo "sin motivo" cuando un compañero la ejecuta de forma concurrente, o cuando dos trabajos/pipelines de CI golpean el mismo entorno.
* Los fallos son **transitorios y no reproducibles**: volver a ejecutar la misma prueba normalmente hace que pase.
* Los fallos se concentran alrededor de un recurso compartido, mutable y persistente: una única base de datos de pruebas compartida, un bucket/cola/caché de S3 compartido, o una cuenta/tenant fijo.
* Las formas del error son delatoras: violaciones de clave duplicada / restricción de unicidad, `expected 1 row but found 2`, o `record not found` (la ejecución de otra persona lo eliminó).

```js
// Ambas pruebas asumen que son dueñas exclusivas de una BD de pruebas COMPARTIDA.
test('creates a user', async () => {
  await db.users.insert({ id: 42, email: 'alice@example.com' }); // clave codificada a mano
  expect(await db.users.findById(42)).toMatchObject({ email: 'alice@example.com' });
});

test('counts active users', async () => {
  expect(await db.users.countActive()).toBe(1); // asume que nadie más añadió filas
});

```

Cuando dos ejecutores golpean la misma base de datos a la vez, el insert choca en `id: 42` (clave duplicada) y el conteo global ya no es `1`. Cada prueba pasa por sí sola; juntas-pero-concurrentes, pelean.

## Reasons for the Problem

**Por qué ocurre.** Las pruebas se apoyan en un **Shared Fixture** global —normalmente un único recurso persistente (una BD de pruebas compartida, un bucket compartido, un usuario fijo) en lugar de un **Fresh Fixture** construido por prueba. Los identificadores codificados a mano y las aserciones sobre el estado global asumen de forma silenciosa que la prueba es _dueña_ del recurso. Mientras exactamente una ejecución lo toque a la vez, la ilusión se mantiene. Añade concurrencia —un segundo desarrollador, trabajos de CI en paralelo o workers de pruebas en paralelo— y las ejecuciones se entrelazan sobre las mismas filas y claves.

**Por qué duele.** Meszaros lo clasifica como una forma de **Erratic Test**: es esencialmente un problema de Interacting Tests donde la interacción se da _entre ejecuciones de pruebas concurrentes_ en lugar de entre pruebas de una misma ejecución.

* **Fiabilidad:** los fallos son no deterministas y dependen de quién más esté ejecutando, por lo que no pueden reproducirse a demanda.
* **Falsa confianza y pérdida de confianza:** el equipo aprende a "simplemente volver a ejecutarla" y empieza a restar importancia a las compilaciones en rojo, incluidas las regresiones reales que se esconden entre el ruido.
* **Mantenibilidad / depurabilidad:** la causa es invisible en la prueba que falla; vive en _otra_ ejecución, y el entrelazado hace brutal encontrar la raíz del problema.
* **Escalabilidad:** bloquea las dos cosas que más quieren los equipos: paralelizar la suite y aumentar el número de personas que pueden ejecutarla a la vez.

## Treatment

Elimina el estado compartido y en disputa. Pasos concretos, aproximadamente en orden de preferencia:

1. **Prefiere un Fresh Fixture.** Cada prueba crea exactamente los datos que necesita y los desmonta —o se ejecuta dentro de una transacción que se revierte en el teardown. No dependas de filas compartidas preexistentes.
2. **Aísla por ejecutor (Database Sandbox / particionado).** Dale a cada desarrollador _y_ a cada trabajo/worker de CI su propia base de datos, esquema o namespace (un esquema por worker, una BD de contenedor efímera o una base de datos de rama). Así, las ejecuciones concurrentes simplemente no pueden verse entre sí.
3. **Usa Distinct Generated Values para las claves.** Reemplaza los ids/correos codificados a mano con valores únicos generados (UUID, secuencia o `timestamp + workerId`) para que las ejecuciones concurrentes creen objetos _distintos_ en lugar de chocar.
4. **No hagas aserciones sobre conteos globales.** Acota las aserciones a los datos que creó esta prueba (filtrando por su clave/tenant único) en lugar de `count == 1`.
5. **Haz que los recursos externos sean por prueba/por worker.** Directorios temporales únicos, nombres únicos de cola/topic, una BD en memoria o de testcontainer por proceso.

```js
// ANTES: clave codificada a mano + aserción global contra una BD compartida
await db.users.insert({ id: 42, email: 'alice@example.com' });
expect(await db.users.countActive()).toBe(1);

```

```js
// DESPUÉS: clave única + aserción acotada (Fresh Fixture + Distinct Generated Values)
const id = crypto.randomUUID();
const email = `alice+${id}@example.com`;
await db.users.insert({ id, email });
expect(await db.users.findById(id)).toMatchObject({ email });
// ...y apunta cada worker a su propio esquema/base de datos sandbox

```

Nota: no hay linter para esto: solo aflora en tiempo de ejecución bajo concurrencia. Hazlo reproducible ejecutando deliberadamente la suite dos veces en paralelo contra el mismo entorno (o aumentando los workers del ejecutor de pruebas) en CI; si eso pone la suite en rojo, tienes un Test Run War que arreglar.
