Quase todo sistema brasileiro tem um formulário que pede CPF. E quase toda suíte de teste desse sistema tem, em algum canto, um "123.456.789-09" fixo no código.

Funciona por um tempo. Depois aparecem três problemas, quase sempre nesta ordem.

1. O CPF repetido

123.456.789-09 passa no dígito verificador. O problema começa quando o segundo teste cadastra outro usuário com o mesmo CPF e o banco devolve duplicate key value violates unique constraint. A saída de sempre é uma lista de CPFs "que funcionam" num arquivo compartilhado, que ninguém sabe de onde veio e que acaba no meio dos dados de produção.

2. O número com cara de CPF que não é CPF

A segunda tentativa costuma ser gerar onze dígitos aleatórios e aplicar a máscara. O formato fica certo, mas o validador do back-end rejeita: só 1 em cada 100 sequências aleatórias acerta os dois dígitos verificadores.

Gerar um CPF válido é curto. Os dois últimos dígitos saem de uma soma ponderada módulo 11:

function gerarCPF(): string { const n = Array.from({ length: 9 }, () => Math.floor(Math.random() * 10)); // 111.111.111-11 e afins passam na conta, mas são recusados if (n.every((d) => d === n[0])) return gerarCPF(); const digito = (base: number[]) => { const peso = base.length + 1; const soma = base.reduce((acc, d, i) => acc + d * (peso - i), 0); const resto = soma % 11; return resto < 2 ? 0 : 11 - resto; }; n.push(digito(n)); // pesos 10 a 2 n.push(digito(n)); // pesos 11 a 2 return n.join("").replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4"); } 

Se o seu teste só precisa de CPF, pode parar aqui: copie a função e siga em frente.

3. Os campos que não batem entre si

O terceiro problema aparece quando o sistema começa a cruzar dados. Antifraude, KYC e checkout com cartão olham se as peças fazem sentido juntas:

  • o DDD do celular é de uma cidade e o CEP é de outro estado;
  • o e-mail não tem nada a ver com o nome;
  • o cartão está no nome de outra pessoa;
  • a chave Pix não é o CPF, o e-mail nem o celular do cliente.

Cada campo isolado é válido, mas o cadastro como um todo é estranho, e o teste falha em lugar que não deveria. Ou pior: passa, porque o fixture nunca chegou perto do caso real.

Daí para frente a função de CPF vira um projeto: CNPJ (e o CNPJ alfanumérico, que a Receita começou a emitir em julho de 2026), cartão com Luhn, Pix nos quatro formatos do BACEN, DDD por cidade, banco com código COMPE de verdade.

O que eu acabei construindo

Passei por esses três problemas e montei o FakeForge, um gerador de dados brasileiros para teste com API. Aviso logo que o projeto é meu, então leve isso em conta. A API responde sem cadastro:

curl -s "https://fakeforge.com.br/api/generate?type=cpf&quantity=3" 
{ "type": "cpf", "quantity": 3, "data": ["286.908.784-59", "814.125.695-59", "016.709.862-41"] } 

Para o problema 3, há os presets: cada item é um cliente inteiro, com os campos coerentes entre si.

curl -s "https://fakeforge.com.br/api/generate?preset=customer&quantity=1" 
{ "nome": "Vinícius Campos Cunha", "cpf": "236.726.684-00", "email": "vinicius_campos694@hotmail.com", "telefone": "(85) 96986-1744", "endereco": { "cep": "60034-397", "street": "Avenida Presidente Kennedy, 1194", "neighborhood": "Dionísio Torres", "city": "Fortaleza", "state": "Ceará", "stateCode": "CE" } } 

DDD 85, CEP de Fortaleza e e-mail derivado do nome. O preset fintech vai além: as chaves Pix são o CPF, o e-mail e o celular do próprio cliente, e o cartão sai no nome dele.

Também dá para pedir em SQL ou CSV, com &format=sql ou &format=csv. O SQL já vem com o CREATE TABLE e o endereço achatado em colunas (endereco_cep, endereco_city...).

Nos testes

Existem SDKs para Node e Python, os dois sem dependência (npm install fakeforge-br e pip install fakeforge-br). A chave de API é lida da variável FAKEFORGE_API_KEY; sem ela, o SDK funciona no modo sem cadastro.

Playwright

import { test, expect } from "@playwright/test"; import { FakeForge } from "fakeforge-br"; const ff = new FakeForge(); test("cadastro com cliente novo a cada execução", async ({ page }) => { const [cliente] = await ff.customer(1); await page.goto("/cadastro"); await page.fill("#nome", cliente.nome); await page.fill("#cpf", cliente.cpf); await page.fill("#email", cliente.email); await page.fill("#telefone", cliente.telefone); await page.fill("#cep", cliente.endereco.cep); await page.click("button[type=submit]"); await expect(page.getByText("Conta criada")).toBeVisible(); }); 

Um cliente novo a cada execução acaba com o erro de CPF repetido sem precisar limpar o banco entre os testes.

pytest

# conftest.py import pytest from fakeforge import FakeForge ff = FakeForge() @pytest.fixture def cliente(): [c] = ff.customer(1) return c 
# test_cadastro.py ("api" é o cliente HTTP do seu projeto) def test_cadastro_aceita_cpf(api, cliente): resposta = api.post("/clientes", json=cliente) assert resposta.status_code == 201 

Seed do banco no GitHub Actions

- name: Seed com 100 clientes env: FAKEFORGE_API_KEY: ${{ secrets.FAKEFORGE_API_KEY }} run: | curl -s -H "X-API-Key: $FAKEFORGE_API_KEY" \ "https://fakeforge.com.br/api/generate?preset=customer&quantity=100&format=sql" \ > seed.sql psql "$DATABASE_URL" -f seed.sql 

A autenticação é pelo cabeçalho X-API-Key, não por Authorization: Bearer.

Limites

Prefiro deixar os limites claros antes que alguém descubra no meio do pipeline:

  • Sem chave: 50 chamadas por dia por IP e até 50 itens por chamada.
  • Com chave grátis: as mesmas 50 chamadas por dia, com até 100 itens por chamada.
  • Presets (clientes, pedidos): sem chave ou no plano grátis vem uma amostra de 3. O seed de 100 clientes acima precisa do plano pago.

Para testar um formulário ou rodar uma suíte pequena, o grátis sobra. Para seed grande ou CI rodando o dia todo, aí entram os planos pagos.

Qual é a sua?

O que mais quebra nos seus testes com dado brasileiro? Me conta nos comentários. Estou decidindo o que vem depois (inscrição estadual, RG por estado, CNH, boleto) e prefiro decidir com base em quem usa.