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.
Top comments (0)