← Python na prática
09 / 10 · 50 MIN

Automatizações testáveis e contratos de execução

Separa lógica e efeitos, desenha testes que detetam falhas e torna explícito o contrato do scheduler.

Uma entrada de execução sem efeitos na importação

Um módulo de suporte deve poder ser importado sem enviar relatórios ou iniciar jobs. Mantém funções reutilizáveis separadas de main e chama main sob a guarda __name__ == "__main__". Define também o contrato do processo: mensagens nos canais previstos e um código inteiro que o scheduler consiga interpretar. Uma string devolvida por main e passada a sys.exit não significa sucesso; gera código 1 e texto em stderr. Com argparse, uma entrada inválida é normalmente rejeitada antes da execução, com código 2. Num ensaio, confirma separadamente importação, argumentos válidos, argumentos inválidos e conclusão do trabalho.

Testar uma obrigação que pode falhar

Antes de escrever o teste, identifica a obrigação observável. Se uma quantidade deve ser um inteiro entre zero e cem, cobre zero, cem e valores imediatamente fora dos limites, além de um caso normal. Para exigir uma exceção, usa assertRaises; um try que simplesmente ignora ValueError também pode passar quando nada é rejeitado. Testa o resultado e os efeitos permitidos, incluindo a ausência de envio quando a validação falha. Um teste verde só demonstra as condições que foram realmente exercitadas. Regista as fronteiras que ainda dependem de integração, concorrência real ou dados com dimensão representativa.

Substituir a dependência efetivamente utilizada

Um patch precisa de atingir o nome que o código procura. Se pipeline importou deliver diretamente, substituir apenas sender.deliver depois dessa importação pode deixar pipeline com a referência anterior. Usa um sentinela local para demonstrar qual função foi chamada, sem enviar dados para a rede. Autospec ajuda a detetar argumentos incompatíveis, mas não comprova o schema de uma resposta. Configura retornos, exceções e sequências coerentes com o contrato. Num teste de retoma, verifica a identidade de cada chamada, o limite de tentativas e o resultado comunicado. A deduplicação no serviço externo exige documentação e evidência próprias.

Fixtures independentes e limpeza previsível

Cada teste deve controlar os recursos e o estado que altera. Regista addCleanup assim que adquirires um recurso que precisa de libertação; esses cleanups podem correr mesmo quando setUp falha e tearDown não é chamado. Usa diretorias e objetos próprios da fixture, sem apagar recursos de outros processos. Se mudares configuração global, restaura-a ou redesenha a dependência para receber configuração explícita. Correr um teste sozinho e em ordem diferente ajuda a descobrir contaminação. Fixar uma ordem favorável ou repetir até ficar verde não demonstra independência e pode ocultar exatamente o comportamento que a suite devia detetar.

Reproduzir o ambiente e interpretar a observabilidade

Um ambiente virtual não deve ser tratado como pasta portável entre máquinas. Recria-o com o intérprete e as dependências previstos e ensaia a chamada usada pelo scheduler. Confirma caminhos, diretoria e configuração, sem depender da ativação manual de um terminal. Nos logs, distingue uma operação de cada emissão do respetivo evento. Um handler no logger filho e outro no root, com propagação ativa para o mesmo destino, podem produzir linhas repetidas. Antes de compensar uma entrega, verifica a referência e o estado no destinatário. Depois corrige a configuração de logging e testa a quantidade de emissões esperada.

from unittest.mock import create_autospec

def send(batch_id, *, timeout):
    raise RuntimeError("external client not configured")

def deliver(batch_id, client):
    response = client(batch_id, timeout=2)
    if not isinstance(response, dict) or response.get("accepted") is not True:
        raise ValueError("invalid acceptance response")
    return batch_id

client = create_autospec(send, return_value={"accepted": True})
assert deliver("TRAIN-204", client) == "TRAIN-204"
client.assert_called_once_with("TRAIN-204", timeout=2)
client.return_value = {"status": "accepted"}
try:
    deliver("TRAIN-205", client)
except ValueError:
    print("response contract rejected")
else:
    raise AssertionError("invalid response accepted")
NA PRÁTICA

Executa o exemplo local: não há cliente de rede configurado. O mock respeita a assinatura, mas a segunda resposta tem outro schema e deve ser rejeitada. Acrescenta um teste com assertRaises e confirma que um timeout posicional é recusado pelo autospec. Este ensaio comprova decisões locais, não o funcionamento de um serviço externo.

Armadilhas comuns

Testes sem asserção; patch no namespace errado; autospec tratado como validação de payload; estado global deixado pela fixture; tearDown assumido após setUp falhado; sucesso comunicado por string; duplicação inferida apenas dos logs.

Tópicos relacionados: Funções pequenas, contratos claros · Erros e ficheiros com contexto · Automação observável e repetível · Subprocessos, caminhos e resultados verificáveis

Leva esta ideia contigo

Um teste útil falha quando a obrigação é violada, controla os seus próprios efeitos e explicita o limite entre evidência local e comportamento externo.

Criar conta

Referência: Python 3.14: Unit testing framework · Python 3.14; DR Python 2026.3

Python® é uma marca registada de Python Software Foundation. A dr.pt é uma plataforma de preparação independente e não está afiliada, associada, patrocinada, autorizada nem aprovada por Python Software Foundation. Os conteúdos e as perguntas são originais, não são perguntas oficiais de exame, e concluir os nossos testes não atribui nem garante qualquer certificação. Os nomes são usados apenas para identificar o tema. Todas as outras marcas pertencem aos respetivos titulares.