Erros comuns de regex para CPF/CNPJ que passam no teste unitário e quebram em produção
Toda base de código acumula, mais cedo ou mais tarde, uma regex para reconhecer CPF ou CNPJ. Ela costuma nascer rápido — alguém copia um padrão de um fórum, ajusta os dígitos, roda contra dois ou três exemplos que tem à mão, os testes passam, o commit sobe. O problema é que uma regex escrita assim tende a acertar exatamente os casos que o desenvolvedor pensou em testar e falhar silenciosamente em todos os outros, porque regex descreve forma, não valida significado, e a diferença entre as duas coisas raramente aparece nos poucos exemplos usados para escrever o teste.
A regex que só reconhece o formato que o autor imaginou
O erro mais comum é escrever a expressão pensando em uma única forma de entrada — normalmente a que aparece formatada com pontuação, como 123.456.789-09 — e esquecer que o mesmo campo pode chegar sem máscara, colado de uma planilha ou digitado por um usuário que ignora os separadores. Uma regex como /^\d{3}\.\d{3}\.\d{3}-\d{2}$/ rejeita de forma correta um CPF mal formatado, mas também rejeita um CPF perfeitamente válido que só não veio com pontuação — e o time descobre isso quando um usuário real reclama, não quando o teste unitário roda, porque o teste unitário só usou o exemplo formatado.
A correção mais robusta normaliza a entrada antes de validar — remove tudo que não é dígito (ou letra, no caso do CNPJ alfanumérico) e só então aplica a regex sobre o resultado — em vez de tentar prever, na própria expressão, todas as formas de pontuação que um usuário pode digitar.
O CNPJ alfanumérico quebra regex antigas sem avisar
Regex escritas antes da migração da Receita Federal para o CNPJ alfanumérico costumam assumir que as 12 primeiras posições são sempre numéricas, algo como /^\d{14}$/. Esse padrão nunca vai lançar um erro chamativo — ele simplesmente vai rejeitar, de forma silenciosa e educada, todo CNPJ real que contenha uma letra, tratando um documento válido como se fosse malformado. É o tipo de bug que passa despercebido em homologação porque a massa de teste da equipe também é inteiramente numérica, e só aparece quando o primeiro CNPJ alfanumérico real bate na API em produção.
Vale reforçar: mesmo no formato novo, os dois últimos dígitos verificadores continuam sempre numéricos — só as 12 primeiras posições podem conter letras. Uma regex atualizada precisa refletir exatamente essa assimetria, não liberar letras em todas as 14 posições.
Expressões sem âncora aceitam lixo nas pontas
Esquecer ^ e $ — ou seus equivalentes de início e fim de string — é um erro sutil porque a maioria dos exemplos de teste usados para validar a regex são strings limpas, sem nada antes ou depois do CPF. Sem essas âncoras, uma regex como /\d{3}\.\d{3}\.\d{3}-\d{2}/ encontra o padrão em qualquer lugar da string, inclusive dentro de um texto maior — o que significa que uma entrada como DROP TABLE users; --123.456.789-09 passa na validação de "contém um CPF", quando o campo deveria conter exclusivamente um CPF e nada mais. Esse tipo de brecha é particularmente perigoso quando o valor validado por regex depois alimenta uma query SQL ou é interpolado em algum comando sem passar por sanitização adicional.
O bug do lastIndex em JavaScript
Em JavaScript, reutilizar a mesma regex com a flag global (g) chamando .test() várias vezes é um clássico gerador de comportamento intermitente. Uma regex global mantém estado entre chamadas através da propriedade lastIndex, então a mesma instância de regex pode retornar true na primeira chamada e false na segunda, para exatamente o mesmo CPF, dependendo de onde a busca anterior parou. Em um teste unitário isolado, cada teste geralmente recria a regex do zero, então o bug nunca aparece; em produção, onde a mesma instância costuma ser reaproveitada — por exemplo, definida uma única vez no topo de um módulo — o comportamento vira dependente da ordem de validações anteriores. A forma mais segura de evitar isso é não usar a flag g em regex usada só para checagem booleana, ou recriar a expressão a cada chamada.
Regex não sabe repetir dígito, nem calcular dígito verificador
Nenhuma regex, por mais elaborada que seja, consegue expressar "os dois últimos dígitos precisam ser o resultado do cálculo de módulo 11 aplicado aos anteriores" — isso é aritmética, não é um padrão de caracteres. Uma regex de CPF bem escrita confirma apenas que a string tem o formato certo: 11 dígitos, ou 11 dígitos com a pontuação no lugar certo. Ela deixa passar, sem nenhum problema, sequências como 111.111.111-11 ou 123.456.789-00, porque ambas têm o formato certo e a regex nunca teve como saber que o dígito verificador está errado. Times que tratam "passou na regex" como sinônimo de "CPF válido" estão, sem perceber, aceitando qualquer sequência de 11 dígitos bem pontuada — a regex é só o primeiro filtro de forma; o cálculo do dígito verificador é uma etapa separada e obrigatória.
Por que o teste unitário não pega nada disso
A explicação é sempre a mesma: os casos de teste escritos junto com a regex tendem a repetir o raciocínio de quem escreveu a regex. Se o autor só pensou em CPF formatado, os testes só cobrem CPF formatado. Se o autor esqueceu do CNPJ alfanumérico, os testes também esqueceram. Regex e seus testes nascem do mesmo ponto cego, então um cobre exatamente as lacunas do outro — o que faz a suíte parecer verde e completa quando na verdade só está validando a própria hipótese inicial, nunca contestada por um caso de fora do que o autor já esperava.
Checklist de casos de teste que expõem esses erros
- Mesmo documento, com e sem pontuação. O mesmo CPF ou CNPJ formatado e sem máscara deve produzir o mesmo resultado de validação.
- CNPJ alfanumérico real. Pelo menos um caso com letra nas 12 primeiras posições, confirmando que a regex (e o cálculo do dígito verificador) trata esse formato.
- Entrada com lixo nas pontas. Uma string com texto antes ou depois do documento, para confirmar que a regex usa âncoras de início e fim.
- Mesma regex, múltiplas chamadas. Em JavaScript, validar o mesmo valor duas vezes seguidas com a mesma instância de regex global, para expor o bug do
lastIndex. - Dígito verificador incorreto com formato correto. Um número com 11 (ou 14) posições e pontuação certa, mas dígito verificador errado, para confirmar que a validação vai além da regex.
- As sequências de dígito repetido.
000.000.000-00até999.999.999-99, que passam na regex e no módulo 11, mas precisam ser rejeitadas por regra explícita.
O Validador de CPF e o Validador de CNPJ deste site já combinam a checagem de formato com o cálculo real do dígito verificador — incluindo o formato alfanumérico do CNPJ — então servem como referência rápida para conferir se um caso de teste específico deveria passar ou falhar. Para montar a massa de teste que exercita esse checklist, o Gerador de CPF e o Gerador de CNPJ produzem documentos estruturalmente válidos, com ou sem pontuação, prontos para virar casos de teste automatizados.