DEV Community

Everton Paula
Everton Paula

Posted on AI-assisted

CPF válido em teste automatizado: o dígito verificador, o CPF repetido e o DDD que não bate

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");
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
{
  "type": "cpf",
  "quantity": 3,
  "data": ["286.908.784-59", "814.125.695-59", "016.709.862-41"]
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
{
  "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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)