Módulo 15 - Testes automatizados

Prática: testar uma função

11 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Escrever uma função de cálculo com uma regra clara e validação de entrada.
  • Cobrir o caso normal e os casos de borda com assert.
  • Reescrever a mesma cobertura como uma classe unittest.
  • Fechar o módulo com um método de testar que dá para repetir sempre.

A função que vamos testar

Nada fixa melhor o módulo do que testar uma função de verdade. Vamos usar um cálculo simples e concreto: o preço de uma corrida de táxi. A regra é uma bandeirada fixa, um valor por quilômetro rodado e a distância percorrida. O preço é a bandeirada mais o valor por quilômetro vezes a distância. É uma função de cálculo pura: dadas as mesmas entradas, sempre devolve o mesmo resultado, o que a torna ideal para testar. Antes de calcular, ela valida a entrada, porque uma distância negativa não faz sentido.

def preco_corrida(distancia_km, bandeirada=5.0, por_km=2.5):
    if distancia_km < 0:
        raise ValueError("distancia nao pode ser negativa")
    return bandeirada + por_km * distancia_km


print(preco_corrida(10))     # 30.0  (5 + 2.5 * 10)
print(preco_corrida(0))      # 5.0   (so a bandeirada)
print(preco_corrida(4, 6))   # 16.0  (6 + 2.5 * 4)

A função de cálculo, com valores padrão de bandeirada e preço por quilômetro.

Antes de testar, pense nos casos como você aprendeu na aula anterior. O caso normal é uma distância comum, como 10 km. A borda mais óbvia é a distância zero, onde o preço deve ser só a bandeirada. Vale testar também uma distância que não seja inteira, e a possibilidade de mudar a bandeirada e o valor por quilômetro. E, claro, a entrada inválida: uma distância negativa, que deve levantar ValueError. Com essa lista na cabeça, escrever os testes vira quase mecânico.

Cobrindo com assert e depois com unittest

Comece pelo jeito mais direto, com asserts soltos em um arquivo. Cada linha afirma um resultado esperado, e a mensagem ajuda se algo falhar. Rode e, se o programa chegar ao print final em silêncio, todos os casos passaram.

assert preco_corrida(10) == 30.0, "caso normal: 10 km"
assert preco_corrida(0) == 5.0, "borda: distancia zero"
assert preco_corrida(2) == 10.0, "2 km: 5 + 2.5 * 2"
assert preco_corrida(4, bandeirada=6, por_km=3) == 18.0, "tarifas custom"

erro = False
try:
    preco_corrida(-1)
except ValueError:
    erro = True
assert erro, "distancia negativa deveria levantar ValueError"

print("Todos os casos passaram!")
# Saida: Todos os casos passaram!

Cobertura com assert: caso normal, bordas, tarifas próprias e a entrada inválida.

Funciona, mas testar a exceção com try, except e uma variável de controle é desajeitado. É exatamente aí que o unittest brilha, com o assertRaises que você viu. Reescreva a mesma cobertura como uma classe de testes: cada caso vira um método test_ com nome descritivo, e a verificação de erro fica limpa. Este é o formato que você levaria para um projeto de verdade.

import unittest


class TestPrecoCorrida(unittest.TestCase):
    def test_caso_normal(self):
        self.assertEqual(preco_corrida(10), 30.0)

    def test_distancia_zero(self):
        self.assertEqual(preco_corrida(0), 5.0)

    def test_tarifas_customizadas(self):
        self.assertEqual(preco_corrida(4, bandeirada=6, por_km=3), 18.0)

    def test_distancia_negativa(self):
        with self.assertRaises(ValueError):
            preco_corrida(-1)


if __name__ == "__main__":
    unittest.main()
# Ran 4 tests in 0.000s
# OK

A mesma cobertura em unittest: quatro comportamentos, quatro métodos, relatório limpo.

Fechando o módulo

Você acabou de percorrer o ciclo inteiro de testar uma função, e é esse ciclo que vai se repetir na sua vida de programador. Primeiro veio a função, com uma regra clara e uma validação que recusa entrada sem sentido. Depois a lista de casos, pensada de propósito para incluir a borda do zero e a entrada negativa, não só o caminho feliz. Então a cobertura, primeiro no assert cru para ver a mecânica e depois no unittest, mais organizado e com o assertRaises resolvendo a verificação de exceção com elegância. O resultado é uma suíte que roda em um instante e que, a partir de agora, protege essa função: se alguém mexer na fórmula ou remover a validação, um teste falha e avisa. Guarde o método dos três passos, porque ele vale para qualquer função de cálculo que você escrever. No próximo módulo, o projeto final junta tudo o que o curso ensinou, e os testes vão ser parte dele.

Teste rápido

Na prática, por que a distância zero e a distância negativa foram testadas além do caso de 10 km?

Perguntas frequentes

Por que essa função de tarifa é boa para praticar testes?
Porque é uma função de cálculo pura: com as mesmas entradas ela sempre devolve o mesmo resultado e não depende de nada externo. Isso torna o teste direto, bastando comparar o valor devolvido com o esperado. Cálculos e regras de negócio são justamente o que mais vale automatizar.
Preciso escrever os testes com assert antes de usar unittest?
Não é obrigatório, foi uma escolha didática para mostrar a mesma cobertura nos dois estilos. Em um projeto real você iria direto ao unittest ou ao pytest. O assert cru serve para entender a mecânica; o framework organiza os testes e trata as exceções com mais elegância.
Como decido quais casos testar em uma função nova?
Siga o método dos três passos: escreva a função com validação, liste os casos (o normal, as bordas como zero e limites, e as entradas inválidas) e transforme cada caso em um teste com nome descritivo. Pergunte-se onde a função poderia tropeçar e escreva um teste para lá.
Por que o unittest ficou mais limpo que os asserts para testar o erro?
Porque testar exceção com assert exige um try, except e uma variável de controle, o que é desajeitado. O unittest oferece with self.assertRaises(ValueError):, que verifica a exceção esperada em uma linha clara. É um bom exemplo de por que os frameworks facilitam a vida.
O que ganho ao deixar esses testes prontos?
Uma rede de proteção. Se alguém mudar a fórmula do preço ou remover a checagem de distância negativa, um teste falha na hora e aponta o problema. Você pode refatorar a função, mudar valores padrão ou reorganizar o código com a segurança de que os comportamentos cobertos continuam valendo.
Esse método serve para qualquer função?
Serve muito bem para funções de cálculo e regras claras, que recebem valores e devolvem um resultado. Funções que dependem de tela, arquivo ou rede pedem técnicas a mais, mas o coração é o mesmo: escolher os casos, comparar obtido e esperado e testar também as bordas e as entradas inválidas.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.