Massa de dados e ambientes de teste: seed, reset e isolamento

Neste guia, você vai entender como organizar massa de dados e ambientes de teste para que a suíte seja previsível, rápida e segura: como separar dev, QA e homologação, como criar um seed determinístico, como expor reset apenas fora de produção, como usar factories, por que dados sintéticos são o caminho certo diante da LGPD e como isolar testes que rodam em paralelo. No final, há um exemplo de endpoint de reset protegido que você pode adaptar.

Trabalho com QA desde 2010 e, em quase todo projeto que acompanhei, a maior fonte de testes instáveis não era o código de teste em si, mas os dados: um registro apagado por outro pipeline, um usuário que alguém bloqueou manualmente, um saldo alterado por um teste anterior. Tratar massa de dados como parte do produto muda esse cenário.

Ambientes: dev, QA e homologação têm papéis diferentes

Cada ambiente existe para responder a uma pergunta diferente, e a estratégia de dados deve acompanhar isso:

  • Dev (local ou efêmero): o desenvolvedor precisa de dados mínimos para rodar a aplicação e os testes de unidade e integração. O banco pode ser recriado a qualquer momento, idealmente em segundos, com containers.
  • QA (testes automatizados e exploratórios): precisa de um estado conhecido antes de cada execução. Aqui o seed determinístico e o reset são essenciais, porque a pipeline roda várias vezes ao dia.
  • Homologação (staging): é o mais próximo de produção em configuração e integrações. Os dados devem ser realistas em formato e volume, mas continuam sendo sintéticos. É onde negócio valida fluxos completos.
  • Produção: não é ambiente de teste. Nenhum endpoint de reset, nenhum usuário com senha conhecida, nenhuma rotina de seed deve existir ali.

Uma regra prática: quanto mais perto de produção, menos o teste deve mexer no estado global e mais ele deve criar os próprios dados e limpá-los depois.

Seed determinístico: o mesmo ponto de partida, sempre

Seed é o conjunto de dados que o ambiente recebe ao ser criado ou resetado. Ele é determinístico quando duas execuções produzem exatamente o mesmo resultado: mesmos IDs, mesmos e-mails, mesmos valores. Isso permite escrever asserções estáveis e reproduzir um bug com o mesmo cenário.

Para chegar lá, evite fontes de aleatoriedade sem controle. Se usar geradores de dados falsos, fixe a semente do gerador. Se precisar de identificadores, prefira valores fixos ou derivados de uma chave estável. Datas relativas ao “agora” também quebram o determinismo; use uma data de referência fixa ou injete o relógio.

// seed.js - executado em dev e QA
const { faker } = require('@faker-js/faker');
faker.seed(20261013); // mesma semente = mesmos dados

const BASE_DATE = new Date('2026-01-01T00:00:00Z');

async function seed(db) {
  await db.users.insertMany([
    { id: 'u-standard', email: 'standard@teste.local', status: 'active' },
    { id: 'u-locked',   email: 'locked@teste.local',   status: 'locked' },
    { id: 'u-slow',     email: 'slow@teste.local',     status: 'active', latencyMs: 3000 },
  ]);

  const products = Array.from({ length: 20 }, (_, i) => ({
    id: `p-${i + 1}`,
    name: faker.commerce.productName(),
    price: Number(faker.commerce.price({ min: 10, max: 500 })),
    createdAt: BASE_DATE,
  }));
  await db.products.insertMany(products);
}
module.exports = { seed };

Versione o seed junto com o código e com as migrations. Quando o schema muda, o seed muda no mesmo pull request, e a revisão de código pega inconsistências antes que a pipeline quebre.

Reset: só fora de produção, e com várias travas

Ter um comando ou endpoint que devolve o ambiente ao estado do seed economiza muito tempo. Pode ser um script de CLI (npm run db:reset), um target de Makefile ou uma rota HTTP usada pela pipeline. O risco é óbvio: se esse recurso chegar a produção, um erro de configuração apaga dados reais.

Por isso, use defesa em camadas:

  • Não registrar a rota em produção: o código do reset só é carregado quando uma variável de ambiente explícita está ativa. Não basta esconder; a rota simplesmente não existe.
  • Verificação dupla de ambiente: além da flag, confira o nome do ambiente e recuse se for produção, mesmo que a flag esteja ligada por engano.
  • Token dedicado: exija um segredo próprio, diferente das credenciais da aplicação, guardado no cofre da pipeline.
  • Restrição de rede: sempre que possível, aceite chamadas apenas da rede interna ou do runner de CI.
  • Log e auditoria: registre quem chamou, quando e de onde.

Factories: dados sob medida para cada teste

O seed cobre o cenário base. Para o resto, use factories: funções que criam um objeto válido com valores padrão e permitem sobrescrever só o que importa para o teste. Assim o teste deixa explícito o que é relevante (“um pedido cancelado”) sem repetir vinte campos obrigatórios.

// factories/order.js
let seq = 0;
function buildOrder(overrides = {}) {
  seq += 1;
  return {
    id: `order-${process.env.TEST_WORKER_ID || 0}-${seq}`,
    customerId: 'u-standard',
    items: [{ productId: 'p-1', qty: 1 }],
    status: 'pending',
    ...overrides,
  };
}

// no teste
const order = await api.createOrder(buildOrder({ status: 'cancelled' }));

Boas factories geram dados válidos por padrão, têm nomes claros e não escondem efeitos colaterais. Se uma factory cria registros relacionados (cliente, endereço, pagamento), deixe isso documentado ou separe em funções diferentes.

Dados sintéticos e LGPD: não use dados reais

Copiar a base de produção para homologação “porque é mais realista” parece prático, mas expõe dados pessoais a mais pessoas, sistemas e logs do que o necessário. A Lei Geral de Proteção de Dados (Lei nº 13.709/2018) estabelece princípios como finalidade, necessidade e segurança no tratamento de dados pessoais (fonte: planalto.gov.br, consultado em 13/10/2026). Ambiente de teste raramente justifica o uso de dados reais de clientes.

O caminho mais seguro é gerar dados sintéticos desde o início:

  • Use nomes, e-mails e telefones fictícios, com domínios reservados para teste (como .local ou example.com) para que nenhum e-mail chegue a uma pessoa real.
  • Para documentos como CPF, gere números com dígitos verificadores válidos, mas sem relação com pessoas reais. O Gerador de massa de testes ajuda nisso, e o Validador de CPF confirma se a regra de validação da sua aplicação está correta.
  • Se for inevitável partir de dados de produção (por exemplo, para reproduzir volume), avalie com o time jurídico e de segurança uma anonimização irreversível antes de qualquer cópia, e não apenas mascaramento na tela.
  • Evite que dados sensíveis apareçam em logs, screenshots e relatórios de teste.

Isolamento entre testes paralelos

Paralelizar a suíte reduz o tempo de execução, mas expõe dependências ocultas. Dois testes que usam o mesmo usuário e alteram o mesmo carrinho vão falhar de forma intermitente. Algumas estratégias, da mais simples à mais robusta:

  • Dados únicos por teste: cada teste cria o que precisa com identificadores exclusivos (prefixo do worker, UUID). O Gerador de UUID é útil para montar casos manuais.
  • Dados do seed apenas para leitura: trate o catálogo e os usuários base como imutáveis. Teste que precisa alterar estado cria a própria cópia via factory.
  • Transação com rollback: em testes de integração, abra uma transação no início e desfaça no fim. Funciona bem quando o teste e a aplicação compartilham a mesma conexão.
  • Schema ou banco por worker: cada processo paralelo recebe seu próprio banco. Exige mais infraestrutura, mas elimina a interferência.
  • Ambiente efêmero por pull request: a aplicação inteira sobe isolada, com seed próprio, e é destruída ao final.

Evite depender da ordem de execução. Se um teste só passa quando outro roda antes, ele não está isolado.

Contas de teste com estados conhecidos

Além de dados genéricos, vale manter um conjunto pequeno de contas com comportamentos específicos e bem documentados. Elas permitem testar caminhos difíceis de reproduzir sob demanda:

  • Usuário padrão: tudo funciona, serve para o caminho feliz.
  • Usuário bloqueado: o login deve falhar com a mensagem correta, sem revelar detalhes demais.
  • Usuário lento: as respostas demoram propositalmente, para validar timeouts, spinners e esperas explícitas na automação.
  • Usuário com erro intermitente ou dados inconsistentes: útil para testar tratamento de falhas e mensagens de erro na interface.

Essas contas devem ser recriadas pelo seed e nunca alteradas por testes. A API de prática do Lab segue essa ideia, com usuários como standard_user e locked_user, e é um bom lugar para treinar asserções sobre cada estado.

Exemplo: endpoint de reset protegido

O exemplo abaixo, em Node.js com Express, junta as travas descritas acima. A rota só é registrada quando ENABLE_TEST_RESET está ativa, recusa produção, exige um token próprio e registra a chamada.

// routes/test-support.js
const crypto = require('crypto');
const { seed } = require('../seed');

function registerTestSupport(app, db) {
  const env = process.env.APP_ENV;          // dev | qa | staging | production
  const enabled = process.env.ENABLE_TEST_RESET === 'true';

  if (!enabled || env === 'production') {
    return; // a rota nem existe
  }

  app.post('/__test__/reset', async (req, res) => {
    const expected = process.env.TEST_RESET_TOKEN || '';
    const received = req.get('X-Test-Reset-Token') || '';

    const ok = expected.length > 0 &&
      expected.length === received.length &&
      crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));

    if (!ok) {
      return res.status(403).json({ error: 'forbidden' });
    }

    console.info('[test-reset]', { env, ip: req.ip, at: new Date().toISOString() });

    await db.dropAllCollections();
    await seed(db);
    return res.status(204).end();
  });
}

module.exports = { registerTestSupport };

Na pipeline, a chamada fica assim, antes da suíte de testes de ponta a ponta:

curl -fsS -X POST "$QA_BASE_URL/__test__/reset" \
  -H "X-Test-Reset-Token: $TEST_RESET_TOKEN"

Complemente com um teste automatizado que garante que, com APP_ENV=production, a rota responde 404. Esse teste simples evita que uma refatoração futura remova a proteção sem ninguém perceber.

Checklist rápido

  • Cada ambiente tem uma estratégia de dados documentada.
  • O seed é versionado, determinístico e atualizado junto com as migrations.
  • O reset não existe em produção e exige token em qualquer outro ambiente.
  • Testes criam os próprios dados com factories e não alteram o seed base.
  • Nenhum dado pessoal real circula em dev, QA ou homologação.
  • Testes paralelos usam identificadores únicos ou bancos isolados.
  • Contas com estados especiais estão documentadas e são recriadas pelo seed.

Próximos passos