Na escrita de testes unitários, verificar se o código lança exceções esperadas é fundamental para garantir a robustez do programa. No entanto, quando as mensagens de exceção contêm várias linhas de informações de pilha ou exigem correspondência de padrões, muitos desenvolvedores enfrentam desafios. Este artigo aborda o uso correto de assertRaises e assertRaisesRegex em Python, ajudando você a dominar a verificação de exceções.
- Afirmações Básicas: Armadilhas e Soluções com assertRaises
1.1 Uso Básico
assertRaises é usado para verificar se o código lança uma exceção específica:
import unittest
def dividir(a, b):
if b == 0:
raise ValueError("O divisor não pode ser zero")
return a / b
class TesteMatematico(unittest.TestCase):
def test_divisao_por_zero(self):
# Uso correto: passando um objeto chamável e seus argumentos
self.assertRaises(ValueError, dividir, 10, 0)
1.2 Erros Comuns
Exemplo Errado: Chamada direta da função, resultando em falha de afirmação
# Errado! A exceção é lançada antes da afirmação
self.assertRaises(ValueError, dividir(10, 0))
- Correspondência Precisa: Técnicas Avançadas com assertRaisesRegex
2.1 Validação com Expressões Regulares
Quando é necesário validar o formato da mensagem de exceção, assertRaisesRegex é a ferramenta mais poderosa:
def test_forca_senha(self):
def senha_fraca(senha):
if len(senha) < 8:
raise ValueError(f"Senha '{senha}' fraca: deve ter pelo menos 8 caracteres")
# Verifica se a mensagem de exceção contém informações-chave
self.assertRaisesRegex(
ValueError,
r"Senha'.*'fraca: deve ter pelo menos\d+ caracteres",
senha_fraca, "12345"
)
2.2 Escape de Caracteres Especiais
Caracteres especiais em expressões regulares devem ser escapados:
# Correspondência de mensagem de erro contendo parênteses
self.assertRaisesRegex(
ValueError,
r"Formato de parâmetro inválido\(exemplo: user:\d+\)",
analisar_entrada, "invalido"
)
- Lidando com Mensagens de Exceção Multilinha
3.1 Armadilhas de Correspondência Multilinha
Quando as exceções incluem informações de pilha, a correspondência padrão não funciona entre linhas:
"""
ValidationError: Formato de dados incorreto
-> Campo 'idade' deve ser um número inteiro
-> Valor de entrada: 'vinte'
"""
3.2 Ativando o Modo DOTALL
Use a flag re.DOTALL para permitir correspondência entre linhas:
import re
self.assertRaisesRegex(
ValidationError,
r"Campo 'idade'.*deve ser um número inteiro.*Valor de entrada: 'vinte'",
validar_perfil, {"idade": "vinte"},
flags=re.DOTALL # Configuração crucial
)
- Evolução Histórica: assertRaisesRegex vs assertRaisesRegexp
4.1 Evolução das Versões
| Método | Versão do Python | Convenção de Nomes |
|---|---|---|
assertRaisesRegexp |
2.7 e anteriores | Estilo camel case antigo |
assertRaisesRegex |
3.2+ | Estilo PEP8 com sublinhado |
4.2 Código Compatível
import unittest
if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
class TestCase(unittest.TestCase):
assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
else:
TestCase = unittest.TestCase
- Prática Real: Do Falho ao Sucesso
5.1 Caso Inicial de Falha
"""
InnerError: Em execução simulada:
Arquivo "app.py", linha 42
processar_dados([])
DataError: Conjunto de dados vazio
"""
# Afirmação inicial falha
self.assertRaisesRegex(InnerError, "Conjunto de dados vazio", processar_dados, [])
5.2 Correção Passo a Passo
-
Escape de Caracteres Especiais``` r"Conjunto de dados vazio" → r"Conjunto de dados vazio" # Sem caracteres especiais, mantém-se inalterado
-
Ativar Correspondência Multilinha``` flags=re.DOTALL
-
Expressão Regular Completa``` r"Em execução simulada:.*DataError: Conjunto de dados vazio"
5.3 Versão Final de Sucesso
self.assertRaisesRegex(
InnerError,
r"Em execução simulada:.*DataError: Conjunto de dados vazio",
processar_dados,
[],
flags=re.DOTALL
)
- Resumo de Práticas Recomendadas
| Cenário | Solução | Exemplo |
|---|---|---|
| Verificação básica de tipo de exceção | assertRaises |
Verificar ValueError lançado |
| Validação de formato de mensagem de exceção | assertRaisesRegex |
Correspondência r"ID inválido:\\d+" |
| Correspondência de informações de pilha multilniha | Flag re.DOTALL |
Correspondência entre linhas de detalhes de erro |
| Tratamento de caracteres especiais | Escape de regex | \\( para corresponder a parênteses literais |
| Compatibilidade entre versões do Python | Tratamento de alias | Seleção automática do nome do método |
- Considerações Adicionais
-
Considerações de Desempenho
- Expressões regulares complexas podem afetar a velocidade dos testes; use
^e$para limitar a correspondência e evite o uso excessivo de.\*.
- Expressões regulares complexas podem afetar a velocidade dos testes; use
-
Métodos de Afirmação Personalizados``` def assert_erro_validacao(self, func, *args): self.assertRaisesRegex( ValidationError, r"[COD_ERR:\d+].+", func, *args, flags=re.DOTALL|re.MULTILINE )
-
Internacionalização de Mensagens de Exceção``` self.assertRaisesRegex( ValueError, r"(Entrada inválida|输入无效)", funcao_multi_idioma, idioma='ambos' )
Com essas técnicas, você estará bem equipado para lidar com cenários de validação de exceções complexos, fortalecendo a qualidade do seu código. Lembre-se: bons testes não apenas cobrem fluxos normais, mas também capturam eficientemente situações anormais.