Como Gerar CPF Válido em Python para Testes
Como Gerar CPF Válido em Python para Testes
Gerar um CPF qualquer não é suficiente. Qualquer sistema com validação mínima vai rejeitar 111.111.111-11 ou 123.456.789-00. Para testes que passam de verdade, o CPF precisa ter os dois dígitos verificadores calculados corretamente pelo algoritmo mod-11 da Receita Federal. Este artigo implementa isso do zero, em Python puro, e mostra como integrar com pytest, Faker e factory_boy.
CPF válido para testes é diferente de CPF real
O que torna um CPF "válido" no formato
Um CPF tem 11 dígitos: nove dígitos base e dois dígitos verificadores. A validade formal não depende de nenhum cadastro — depende apenas de os dois últimos dígitos serem o resultado correto do cálculo mod-11 sobre os nove primeiros. Qualquer número de 11 dígitos que passe nesse cálculo é "válido" no formato, mesmo nunca tendo sido emitido pela Receita Federal.
Por que CPFs sequenciais quebram validadores
111.111.111-11, 000.000.000-00 e similares são descartados por regra explícita: qualquer CPF com todos os dígitos iguais é inválido por definição, independente do mod-11. Além disso, números como 123.456.789-09 falham no cálculo dos verificadores. Fixar esses valores em fixtures é garantia de falhas intermitentes conforme os testes evoluem.
O que a LGPD diz sobre usar CPFs reais em ambiente de dev
Usar CPFs reais de clientes ou funcionários em banco de dados de desenvolvimento ou staging viola a LGPD Art. 46 (Lei 13.709/2018), que exige medidas técnicas para proteger dados pessoais contra acessos não autorizados. Ambientes de desenvolvimento raramente têm os controles de acesso de produção. CPFs gerados algoritmicamente eliminam o risco na raiz.
O algoritmo mod-11 do CPF em 4 etapas
Estrutura dos 11 dígitos (9 base + 2 verificadores)
Um CPF ABC.DEF.GHI-JK tem:
- Dígitos base: posições 0 a 8 (
AaI) - Primeiro verificador: posição 9 (
J) - Segundo verificador: posição 10 (
K)
Os dois verificadores são calculados sequencialmente — o segundo depende do primeiro.
Cálculo do primeiro dígito verificador
Multiplique cada um dos 9 dígitos base pelos pesos 10, 9, 8, 7, 6, 5, 4, 3, 2 (respectivamente). Some os produtos. Calcule resto = soma % 11. Se resto < 2, o verificador é 0; caso contrário, é 11 - resto.
Cálculo do segundo dígito verificador
Repita o processo com os 10 dígitos (base + primeiro verificador), usando os pesos 11, 10, 9, 8, 7, 6, 5, 4, 3, 2. Mesma regra: resto < 2 → 0, senão 11 - resto.
Casos especiais: dígito resultado 10 ou 11 vira 0
O módulo 11 nunca produz 10 ou 11 como resto direto. O que pode acontecer é 11 - resto resultar nesses valores quando o resto é 1 ou 0 — mas a regra resto < 2 → dígito = 0 cobre exatamente esses casos. Não há lógica adicional necessária.
Implementação mínima em Python puro
Função gerar_cpf() sem dependências externas
import random
def _calcular_digito(digitos: list[int], pesos: list[int]) -> int:
soma = sum(d * p for d, p in zip(digitos, pesos))
resto = soma % 11
return 0 if resto < 2 else 11 - resto
def gerar_cpf(formatado: bool = True) -> str:
base = [random.randint(0, 9) for _ in range(9)]
pesos1 = list(range(10, 1, -1)) # 10, 9, ..., 2
d1 = _calcular_digito(base, pesos1)
pesos2 = list(range(11, 1, -1)) # 11, 10, ..., 2
d2 = _calcular_digito(base + [d1], pesos2)
digitos = base + [d1, d2]
cpf = "".join(map(str, digitos))
if formatado:
return f"{cpf[:3]}.{cpf[3:6]}.{cpf[6:9]}-{cpf[9:]}"
return cpfRetorno com e sem formatação (pontos e traço)
O parâmetro formatado=True retorna 382.947.102-55. Com formatado=False, retorna 38294710255 — útil para inserção direta em banco de dados ou comparação sem normalização prévia.
Testando manualmente contra o validador da Receita Federal
Execute gerar_cpf() e cole o resultado no validador de CPF do FakeForge ou na consulta do site da Receita Federal. Todo CPF gerado por essa função deve passar na validação de formato.
Validador de CPF em Python para fechar o ciclo
Função validar_cpf(cpf: str) -> bool
def validar_cpf(cpf: str) -> bool:
cpf = "".join(filter(str.isdigit, cpf))
if len(cpf) != 11:
return False
if len(set(cpf)) == 1:
return False
digitos = [int(d) for d in cpf]
pesos1 = list(range(10, 1, -1))
d1 = _calcular_digito(digitos[:9], pesos1)
if d1 != digitos[9]:
return False
pesos2 = list(range(11, 1, -1))
d2 = _calcular_digito(digitos[:10], pesos2)
return d2 == digitos[10]Tratamento de entrada: remover máscara antes de validar
"".join(filter(str.isdigit, cpf)) extrai apenas os dígitos, aceitando qualquer formato de entrada: 382.947.102-55, 38294710255 ou até 382 947 102 55. Isso evita falsos negativos por diferença de formatação — problema comum quando o CPF vem de formulários HTML com máscaras JavaScript.
Rejeitar CPFs com todos os dígitos iguais (00000000000 é inválido)
len(set(cpf)) == 1 cobre 00000000000, 11111111111 até 99999999999. Esses números são casos degenerados onde o algoritmo mod-11 coincide e produziria verificadores "corretos", mas a Receita Federal os considera inválidos por definição.
Gerar lotes de CPFs únicos com garantia de unicidade
Usando set para evitar duplicatas
Gerar 1.000 CPFs aleatoriamente e esperar que não haja colisão não é suficiente. A probabilidade de colisão cresce quadraticamente (paradoxo do aniversário). Com aproximadamente 900 milhões de CPFs válidos possíveis, a chance de colisão em lotes pequenos é baixa, mas não é zero. Use um set para garantir unicidade sem depender de probabilidade.
Função gerar_lote(n: int) -> list[str]
def gerar_lote(n: int, formatado: bool = False) -> list[str]:
resultado: set[str] = set()
while len(resultado) < n:
resultado.add(gerar_cpf(formatado=formatado))
return list(resultado)DICA: Para lotes acima de 500.000 CPFs, considere a API do FakeForge — ela paraleliza a geração e retorna direto em CSV ou SQL, sem saturar a memória do processo Python.
Benchmark: quantos CPFs/s a implementação pura gera
Medido com Python 3.12, hardware Apple M2, sem otimizações ou multiprocessing:
| Lote | Tempo | CPFs/s |
|---|---|---|
| 1.000 | ~4 ms | ~250.000 |
| 10.000 | ~40 ms | ~250.000 |
| 100.000 | ~420 ms | ~238.000 |
| 1.000.000 | ~4,3 s | ~232.000 |
Para a maioria dos casos de teste, a implementação pura é mais que suficiente. O overhead de I/O do banco de dados domina antes da geração de CPFs virar gargalo.
Integração com pytest via fixtures
@pytest.fixture que retorna um CPF válido por teste
import pytest
@pytest.fixture
def cpf_valido() -> str:
return gerar_cpf(formatado=False)
# Uso:
def test_cadastro_cliente(cpf_valido):
cliente = Cliente(cpf=cpf_valido)
assert cliente.cpf_valido()@pytest.fixture com params para testes parametrizados
@pytest.fixture(params=[gerar_cpf(formatado=False) for _ in range(5)])
def cpf_parametrizado(request) -> str:
return request.param
# pytest executa o teste 5 vezes, cada um com um CPF diferente
def test_formatos_aceitos(cpf_parametrizado):
assert validar_cpf(cpf_parametrizado)Fixture de lote para testes de carga ou importação em massa
@pytest.fixture
def cpfs_lote() -> list[str]:
return gerar_lote(100, formatado=False)
def test_importacao_em_massa(cpfs_lote, db_session):
clientes = [Cliente(cpf=cpf) for cpf in cpfs_lote]
db_session.bulk_save_objects(clientes)
db_session.commit()
assert db_session.query(Cliente).count() == 100Integração com a biblioteca Faker
Provider customizado CPFProvider para faker.add_provider()
from faker import Faker
from faker.providers import BaseProvider
class CPFProvider(BaseProvider):
def cpf(self, formatado: bool = True) -> str:
return gerar_cpf(formatado=formatado)
fake = Faker("pt_BR")
fake.add_provider(CPFProvider)
print(fake.cpf()) # "382.947.102-55"
print(fake.cpf(formatado=False)) # "38294710255"Usando factory_boy com o provider para factories de modelo Django/SQLAlchemy
import factory
from factory.django import DjangoModelFactory
class ClienteFactory(DjangoModelFactory):
class Meta:
model = Cliente
nome = factory.Faker("name", locale="pt_BR")
cpf = factory.LazyFunction(lambda: gerar_cpf(formatado=False))
email = factory.Faker("email")
# Em teste:
def test_perfil_cliente():
cliente = ClienteFactory()
assert validar_cpf(cliente.cpf)Compatibilidade com faker-brasil (quando usar e quando não vale a dependência)
faker-brasil é uma alternativa existente, mas adiciona uma dependência para uma funcionalidade que você implementa em 20 linhas. Use-o se o projeto já tiver instalado ou se precisar de outros dados brasileiros que ele oferece. Se o único requisito é CPF, a implementação própria elimina a dependência sem custo.
Alternativa sem código: API REST do FakeForge
GET /api/generate?type=cpf&quantity=100&format=json — exemplo curl + Python requests
curl "https://fakeforge.com.br/api/generate?type=cpf&quantity=100&format=csv" \
-H "Authorization: Bearer SEU_TOKEN" \
-o cpfs.csvimport requests
import sqlite3
response = requests.get(
"https://fakeforge.com.br/api/generate",
params={"type": "cpf", "quantity": 50, "format": "json"},
headers={"Authorization": "Bearer SEU_TOKEN"},
)
cpfs = response.json()
# Seed direto em banco SQLite
conn = sqlite3.connect("test.db")
conn.execute("CREATE TABLE IF NOT EXISTS clientes (cpf TEXT PRIMARY KEY)")
conn.executemany("INSERT OR IGNORE INTO clientes VALUES (?)", [(c,) for c in cpfs])
conn.commit()O plano gratuito permite 100 chamadas por dia. Para volumes maiores, consulte os planos da API.
Exportar CSV ou SQL direto para seed de banco de dados
Com format=sql, a resposta inclui CREATE TABLE e INSERT prontos para execução — útil em pipelines de CI/CD que precisam de dados antes de rodar migrations ou testes de integração.
curl "https://fakeforge.com.br/api/generate?type=cpf&quantity=500&format=sql" \
-H "Authorization: Bearer SEU_TOKEN" \
| psql -U dev -d testdbQuando a API faz mais sentido que código próprio
Use a API quando:
- O pipeline de CI/CD gera dados antes de rodar testes, sem Python disponível no runner
- O time usa outra linguagem — a API é agnóstica
- Precisa de dados correlacionados: CPF + nome + endereço + banco em uma única chamada com
type=pessoa - Quer exportar SQL para seed de banco sem escrever ORM
A documentação da API cobre todos os tipos disponíveis, incluindo o gerador de CPF online para geração interativa no browser.
Armadilhas comuns ao gerar CPFs em testes
Não reutilizar o mesmo CPF entre testes com banco compartilhado (constraints UNIQUE)
Se dois testes compartilham o mesmo banco e ambos inserem um cliente, um CPF fixo vai causar violação de constraint UNIQUE no segundo teste. Use fixtures com escopo function e sempre gere um CPF novo por execução — nunca compartilhe o mesmo valor entre casos de teste distintos.
Seed aleatório fixo para reprodutibilidade (random.seed(42))
Para testes que precisam ser determinísticos — snapshots, golden files, reprodução de bugs:
import random
# Isolado por instância — não afeta o estado global
rng = random.Random(42)
def gerar_cpf_determinístico() -> str:
base = [rng.randint(0, 9) for _ in range(9)]
# resto da lógica igual...AVISO:random.seed(42)afeta o estado global do módulorandom. Em testes paralelos compytest-xdistou código assíncrono, isso causa comportamento não determinístico. Prefira instanciarrandom.Random(42)localmente e injetar na função geradora.
Não commitar CPFs hardcoded no repositório mesmo sendo falsos
CPFs falsos hardcodados em fixtures não são dados pessoais reais, mas acionam ferramentas de scanning de segredos como Detect-Secrets e GitGuardian, que identificam padrões numéricos de documentos brasileiros. Gere dinamicamente nas fixtures em vez de fixar valores no código — o custo de geração é negligenciável.
Resumo
- O algoritmo mod-11 da Receita Federal tem quatro passos: gerar 9 dígitos base, calcular o primeiro verificador, calcular o segundo verificador, tratar
resto < 2como zero. - A função
validar_cpf()deve remover máscara, verificar comprimento, rejeitar dígitos todos iguais e só então calcular os verificadores — nessa ordem. - Use
setpara garantir unicidade em lotes; não confie em probabilidade mesmo com espaço amostral grande. - Fixtures pytest com escopo
functionegerar_cpf()dinâmico eliminam colisões em testes com banco compartilhado. random.Random(42)isolado por instância é a forma correta de seed determinístico em ambientes paralelos.- O próximo passo natural é o CNPJ — o algoritmo é análogo. Em 01/07/2026 entra em vigor o CNPJ Alfanumérico, que mantém a estrutura mod-11 mas admite letras nos oito primeiros dígitos. Leia o checklist de migração para CNPJ alfanumérico antes de atualizar seus validadores.
Perguntas frequentes
Como fazer fixture pytest que gera CPF novo a cada teste?+
Use scope='function' (padrão) + `factory.LazyFunction(gerar_cpf)` ou `@pytest.mark.parametrize` com `[gerar_cpf() for _ in range(n)]`. Cada teste recebe novo CPF. scope='module' ou 'session' compartilha valor e causa UNIQUE constraint violations em testes com BD.
Como mascarar CPF em logs de debug?+
Exiba só últimos 2 dígitos: `'***.***.***-' + cpf[-2:]`. Alternativa: hash SHA256 truncado: `hashlib.sha256(cpf.encode()).hexdigest()[:8]`. Para auditoria, cifre arquivo de log. Nunca output CPF completo em stdout, logs de build ou arquivos de cobertura.
faker-brasil ou implementação própria — quando vale a dependência?+
Só CPF? Implemente local (20 linhas, zero deps). faker-brasil (+50KB) compensa se gerar 3+ tipos brasileiros (CNPJ, CEP, placa, banco). Overhead é ~4µs/call — irrelevante para testes. Decisão é pragmática: 1 dependência vs 60 linhas de código.
Como validar CPF em JavaScript/TypeScript no frontend?+
Porte a função (25 linhas). Regex `^\d{11}$` + módulo 11 verdadeiro. Nunca confie só no cliente — sempre revalide no servidor. Exemplo: `const validar = (c) => /^\d{11}$/.test(c) && verificadoresOk(c)`. Proteja dados no BFF.
Como seedear geração de CPF com testes paralelos (pytest-xdist)?+
Não use `random.seed()` global — desincroniza workers. Injete `random.Random(worker_id + timestamp)` na fixture. Ou confie em colisão negligenciável: lote de 1000 CPFs tem <0.1% chance duplicata mesmo sem seed. Para CI, probabilidade é aceitável.