Técnicas Avançadas de Testes Unitários em Python: Captura Precisa de Mensagens de Exceção

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.

  1. 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))

  1. 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"
)

  1. 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
)

  1. 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

  1. 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

  1. Escape de Caracteres Especiais``` r"Conjunto de dados vazio" → r"Conjunto de dados vazio" # Sem caracteres especiais, mantém-se inalterado

  2. Ativar Correspondência Multilinha``` flags=re.DOTALL

  3. 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
)

  1. 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
  1. Considerações Adicionais

  1. 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 .\*.
  2. 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 )

  3. 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.

Tags: unittest Python assertRaises assertRaisesRegex teste unitário

Publicado em 8-2 13:51