---
title: "Test Run War"
type: "test-smell"
slug: "test-run-war"
url: "http://localhost:3000/pt-br/test-smells/test-run-war.md"
category: "Maus Cheiros Erráticos"
description: "Uma Test Run War acontece quando os testes passam para uma pessoa, mas falham aleatoriamente assim que várias pessoas ou jobs de CI executam a suíte ao mesmo tempo, porque os testes colidem em uma fixture compartilhada e persistente."
---
# Test Run War

> Uma Test Run War acontece quando os testes passam para uma pessoa, mas falham aleatoriamente assim que várias pessoas ou jobs de CI executam a suíte ao mesmo tempo, porque os testes colidem em uma fixture compartilhada e persistente.

## Signs and Symptoms

Você reconhece uma Test Run War por _quem_ e _quando_, não pelo corpo do teste em si:

* A suíte fica verde na sua máquina e em execuções de CI isoladas, mas fica vermelha “sem motivo” quando um colega a executa de forma concorrente, ou quando dois jobs/pipelines de CI atingem o mesmo ambiente.
* As falhas são **transitórias e não reproduzíveis** — reexecutar o mesmo teste normalmente faz com que ele passe.
* As falhas se concentram em torno de um recurso compartilhado, mutável e persistente: um banco de testes compartilhado, um bucket/fila/cache S3 compartilhado ou uma conta/tenant fixa.
* O formato dos erros é revelador: violações de chave duplicada / restrição de unicidade, `expected 1 row but found 2` ou `record not found` (a execução de outra pessoa o apagou).

```js
// Ambos os testes presumem que são donos exclusivos de um banco de testes COMPARTILHADO.
test('creates a user', async () => {
  await db.users.insert({ id: 42, email: 'alice@example.com' }); // chave fixa no código
  expect(await db.users.findById(42)).toMatchObject({ email: 'alice@example.com' });
});

test('counts active users', async () => {
  expect(await db.users.countActive()).toBe(1); // presume que ninguém mais adicionou linhas
});

```

Quando dois executores atingem o mesmo banco ao mesmo tempo, o insert colide em `id: 42` (chave duplicada) e a contagem global deixa de ser `1`. Cada teste passa sozinho; juntos e concorrentes, eles brigam.

## Reasons for the Problem

**Por que acontece.** Os testes se apoiam em uma **Shared Fixture** (fixture compartilhada) global — normalmente um único recurso persistente (um banco de testes compartilhado, um bucket compartilhado, um usuário fixo) em vez de uma **Fresh Fixture** (fixture nova) construída por teste. Identificadores fixos no código e asserções sobre o estado global pressupõem silenciosamente que o teste é _dono_ do recurso. Enquanto apenas uma execução o acessa por vez, a ilusão se mantém. Adicione concorrência — um segundo desenvolvedor, jobs de CI paralelos ou workers de teste paralelos — e as execuções se intercalam nas mesmas linhas e chaves.

**Por que é prejudicial.** Meszaros classifica isso como uma forma de **Erratic Test** (teste errático): é essencialmente um problema de Interacting Tests (testes que interagem) em que a interação ocorre _entre execuções de teste concorrentes_, e não entre testes de uma mesma execução.

* **Confiabilidade:** as falhas são não determinísticas e dependem de quem mais está executando, de modo que não podem ser reproduzidas sob demanda.
* **Falsa confiança e perda de credibilidade:** a equipe aprende a “simplesmente reexecutar” e passa a ignorar builds vermelhos — incluindo as regressões reais escondidas em meio ao ruído.
* **Manutenibilidade / depurabilidade:** a causa é invisível no teste que falha; ela está em _outra_ execução, e a intercalação torna brutal a busca pela causa raiz.
* **Escalabilidade:** isso bloqueia as duas coisas que as equipes mais desejam — paralelizar a suíte e aumentar o número de pessoas que podem executá-la ao mesmo tempo.

## Treatment

Elimine o estado compartilhado e disputado. Passos concretos, aproximadamente em ordem de preferência:

1. **Prefira uma Fresh Fixture.** Cada teste cria exatamente os dados de que precisa e os remove em seguida — ou executa dentro de uma transação que sofre rollback no teardown. Não dependa de linhas compartilhadas preexistentes.
2. **Isole por executor (Database Sandbox / particionamento).** Dê a cada desenvolvedor _e_ a cada job/worker de CI seu próprio banco, schema ou namespace (um schema por worker, um banco efêmero em contêiner ou um banco de branch). Assim, execuções concorrentes simplesmente não enxergam umas às outras.
3. **Use Distinct Generated Values (valores gerados distintos) para as chaves.** Substitua ids/e-mails fixos no código por valores únicos gerados (UUID, sequência ou `timestamp + workerId`), de modo que execuções concorrentes criem objetos _distintos_ em vez de colidir.
4. **Não faça asserções sobre contagens globais.** Limite as asserções aos dados que este teste criou (filtre pela sua chave/tenant única) em vez de `count == 1`.
5. **Torne os recursos externos por teste/por worker.** Diretórios temporários únicos, nomes de fila/tópico únicos, um banco em memória ou testcontainer por processo.

```js
// ANTES: chave fixa no código + asserção global contra um banco compartilhado
await db.users.insert({ id: 42, email: 'alice@example.com' });
expect(await db.users.countActive()).toBe(1);

```

```js
// DEPOIS: chave única + asserção delimitada (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 });
// ...e aponte cada worker para seu próprio banco de schema/sandbox

```

Observação: não existe linter para isso — o problema só aparece em tempo de execução, sob concorrência. Torne-o reproduzível executando deliberadamente a suíte duas vezes em paralelo contra o mesmo ambiente (ou aumentando o número de workers do executor de testes) na CI; se isso deixar a suíte vermelha, você tem uma Test Run War para corrigir.
