Seed data: populando bancos para desenvolvimento e teste

1. O que é Seed Data e por que ela é essencial

Seed data, ou dados de semente, refere-se ao conjunto inicial de informações inseridas em um banco de dados para simular um ambiente realista durante o desenvolvimento e os testes. Diferentemente de fixtures (dados estáticos usados em testes unitários) ou dados de produção mascarados (cópias anonimizadas de dados reais), a seed data é criada artificialmente para atender necessidades específicas de cada contexto.

A importância da seed data se manifesta em três pilares:

  • Consistência entre times: todos os desenvolvedores partem do mesmo estado inicial, eliminando o "funciona na minha máquina"
  • Automação de testes: cenários previsíveis permitem validar comportamentos esperados sem depender de dados externos
  • Onboarding rápido: novos membros da equipe podem começar a trabalhar imediatamente, sem precisar criar dados manualmente

2. Estratégias de Geração de Seed Data

Existem duas abordagens principais para gerar seed data:

Dados estáticos: arquivos SQL fixos com valores pré-definidos. São previsíveis e versionáveis, mas difíceis de manter em grande escala.

-- seeds/001_usuarios.sql
INSERT INTO usuarios (id, nome, email, criado_em) VALUES
(1, 'Ana Silva', 'ana@exemplo.com', '2024-01-01'),
(2, 'Carlos Souza', 'carlos@exemplo.com', '2024-01-02');

Dados dinâmicos: gerados proceduralmente usando bibliotecas como Faker (Python) ou Faker.js. Produzem dados realistas e volumosos.

-- seeds/gerar_usuarios.py (exemplo conceitual)
from faker import Faker
fake = Faker('pt_BR')
for _ in range(100):
    print(f"INSERT INTO usuarios (nome, email) VALUES ('{fake.name()}', '{fake.email()}');")

A recomendação moderna é versionar scripts executáveis (Python, JavaScript, Ruby) em vez de arquivos SQL puros, pois permitem lógica condicional e geração dinâmica.

3. Organização e Estrutura de Seeds no Projeto

Uma estrutura organizada evita confusão e garante reprodutibilidade:

projeto/
├── seeds/
│   ├── dev/
│   │   ├── 001_usuarios.sql
│   │   ├── 002_pedidos.sql
│   │   └── 003_itens_pedido.sql
│   ├── test/
│   │   ├── usuarios_minimos.sql
│   │   └── pedidos_minimos.sql
│   └── common/
│       ├── categorias.sql
│       └── configuracoes.sql

A numeração sequencial (001, 002...) define a ordem de execução, respeitando chaves estrangeiras. Para ciclos (ex.: usuário <-> endereço), use ALTER TABLE ... DISABLE TRIGGER temporariamente ou adie constraints.

4. Técnicas de Inserção Eficiente

Batch inserts: uma única instrução INSERT com múltiplos valores é drasticamente mais rápida que inserts individuais:

-- INEFICIENTE: 1000 inserts separados
INSERT INTO produtos (nome, preco) VALUES ('Produto A', 10.00);
INSERT INTO produtos (nome, preco) VALUES ('Produto B', 20.00);
-- ... 998 inserts

-- EFICIENTE: batch insert
INSERT INTO produtos (nome, preco) VALUES
('Produto A', 10.00),
('Produto B', 20.00),
-- ... até 1000 linhas
('Produto Z', 1000.00);

Resetando identidades: após truncar tabelas, reinicie sequências para evitar conflitos de ID:

TRUNCATE TABLE pedidos, itens_pedido RESTART IDENTITY CASCADE;

Manipulação de sequências: use SETVAL para controlar o próximo valor de uma sequência:

SELECT setval('produtos_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM produtos));

5. Limpeza e Reset de Dados entre Execuções

Três estratégias principais para limpar dados entre execuções:

Estratégia Velocidade Reseta IDs Mantém estrutura Recomendado para
DELETE FROM tabela Lenta Não Sim Pequenos volumes
TRUNCATE tabela Rápida Sim (com RESTART IDENTITY) Sim Volumes médios
Rollback de transação Instantânea Sim Sim Testes unitários

Para testes automatizados, a abordagem mais segura é isolar seeds em uma transação:

BEGIN;
-- Executar seeds aqui
INSERT INTO usuarios (nome, email) VALUES ('Teste', 'teste@exemplo.com');
-- Executar testes...
ROLLBACK; -- Desfaz tudo automaticamente

Scripts de automação podem incluir comandos como:

# seed:clean - remove todos os dados
psql -d meu_banco -c "TRUNCATE SCHEMA public CASCADE;"

# seed:reset - limpa e reinsere
psql -d meu_banco -f seeds/clean.sql
psql -d meu_banco -f seeds/001_usuarios.sql

6. Tratamento de Dados Sensíveis e Mascaramento

Dados pessoais identificáveis (PII) como CPF, e-mail e telefone nunca devem ser commitados. Técnicas de anonimização:

  • Substituição: use dados fictícios gerados por Faker
  • Hash: aplique hash SHA-256 em campos sensíveis (irreversível)
  • Ofuscação: troque caracteres internos (ex.: "joão" → "j***")
-- Exemplo de ofuscação de e-mail
UPDATE usuarios SET email = CONCAT(LEFT(email, 1), '***@', SUBSTRING(email FROM POSITION('@' IN email) + 1));

Boas práticas essenciais:
- Nunca commitar dados reais no repositório
- Usar variáveis de ambiente para senhas e tokens
- Validar seeds em CI/CD para detectar vazamentos acidentais

7. Integração com Ferramentas e Frameworks

Prisma:

// prisma/seed.ts
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

async function main() {
  await prisma.usuario.create({
    data: { nome: 'Admin', email: 'admin@exemplo.com' }
  })
}
main()

ActiveRecord (Ruby on Rails):

# db/seeds.rb
User.create!([
  { name: 'Admin', email: 'admin@exemplo.com' },
  { name: 'Teste', email: 'teste@exemplo.com' }
])

Django ORM:

# seeds.py
from meu_app.models import Usuario
Usuario.objects.create(nome='Admin', email='admin@exemplo.com')

Para bancos NoSQL como MongoDB, seeds são inseridos como documentos JSON:

// seeds/usuarios.json
[
  { "nome": "Ana", "email": "ana@exemplo.com", "ativo": true },
  { "nome": "Carlos", "email": "carlos@exemplo.com", "ativo": false }
]

Em pipelines CI/CD, use variáveis de ambiente para controlar execução:

if [ "$SEED_ENV" = "dev" ]; then
  npm run seed:dev
elif [ "$SEED_ENV" = "test" ]; then
  npm run seed:test
fi

8. Boas Práticas e Armadilhas Comuns

Armadilha 1: Seeds monolíticos

-- EVITAR: arquivo único de 2000 linhas
-- seeds/tudo.sql contém INSERTs de todas as tabelas

Solução: modularize por domínio (usuários, pedidos, produtos) e use scripts de orquestração.

Armadilha 2: Seeds desatualizados

Quando o schema evolui (novas colunas, constraints), seeds quebram silenciosamente. Mantenha seeds sincronizados com migrations, executando-os como parte do pipeline de CI.

Armadilha 3: Ignorar constraints

-- Isso falha se a tabela categorias estiver vazia
INSERT INTO produtos (nome, categoria_id) VALUES ('Mouse', 999);

Solução: sempre inserir dados pai antes dos filhos, ou usar DEFERRABLE INITIALLY DEFERRED para constraints.

Boas práticas finais:
- Teste seeds em pipeline: valide integridade referencial e constraints
- Use seeds diferentes por ambiente (dev: volumosos; test: mínimos)
- Documente a ordem de execução e dependências entre seeds


Referências