Como Fazer Testes Unitários no Python

Tempo de leitura: 18 minutos
Logo do Python com o texto 'Testes Unitários' abaixo

Você já se perguntou como os grandes projetos de software conseguem manter milhares de linhas de código funcionando sem erros? A resposta está nos testes unitários. Neste guia completo, você vai aprender como fazer testes unitários no Python e transformar a qualidade do seu código.

O Que São Testes Unitários e Por Que São Importantes

Testes unitários são pequenos pedaços de código que verificam se partes específicas do seu programa funcionam corretamente. Imagine que você está construindo um carro. Antes de testar o veículo completo, você testa cada componente separadamente: o motor, os freios, as rodas. É exatamente isso que os testes unitários fazem com o seu código.

Quando você escreve uma função em Python, ela pode ter dezenas de comportamentos diferentes dependendo dos dados que recebe. Testar manualmente cada cenário toda vez que você faz uma mudança no código é demorado e sujeito a erros. Com testes unitários, você automatiza esse processo.

Para entender melhor como funcionam os testes unitários no Python, confira este vídeo tutorial do canal Muri Tech que explica os conceitos fundamentais de forma clara e prática:

YouTube player

Os benefícios dos testes unitários vão muito além de encontrar bugs. Eles ajudam você a escrever código mais organizado, facilitam a manutenção futura e dão confiança para fazer mudanças sem medo de quebrar algo que já estava funcionando. Empresas como Google, Facebook e Netflix investem pesadamente em testes automatizados justamente por esses motivos.

Conhecendo as Principais Bibliotecas de Teste

Python oferece várias ferramentas para criar testes unitários. As duas mais populares são unittest e pytest. Vamos entender as diferenças entre elas e quando usar cada uma.

Unittest: A Biblioteca Nativa

O unittest vem instalado automaticamente quando você faz a instalação do Python. Isso significa que você pode começar a escrever testes imediatamente, sem precisar instalar nada extra. A biblioteca foi inspirada no JUnit, uma ferramenta popular para testes em Java.

O unittest usa uma abordagem baseada em classes. Você cria uma classe que herda de unittest.TestCase e define métodos de teste dentro dela. Cada método de teste deve começar com a palavra test_ para que o framework reconheça automaticamente.

Python
import unittest

def somar(a, b):
    return a + b

class TestCalculadora(unittest.TestCase):
    def test_somar_positivos(self):
        resultado = somar(2, 3)
        self.assertEqual(resultado, 5)
    
    def test_somar_negativos(self):
        resultado = somar(-1, -1)
        self.assertEqual(resultado, -2)

if __name__ == '__main__':
    unittest.main()

O código acima mostra um teste básico com unittest. Criamos uma função simples de soma e testamos dois cenários diferentes. O método assertEqual verifica se o resultado obtido é igual ao resultado esperado.

Pytest: Simplicidade e Poder

O pytest é a biblioteca de testes mais popular da comunidade Python. Ela é conhecida por sua sintaxe simples e recursos avançados. Para usar o pytest, você precisa instalá-lo primeiro através do pip.

Bash
pip install pytest

A grande vantagem do pytest é que você não precisa criar classes. Basta escrever funções normais e usar a palavra assert do Python. Veja como fica o mesmo teste anterior usando pytest:

Python
def somar(a, b):
    return a + b

def test_somar_positivos():
    assert somar(2, 3) == 5

def test_somar_negativos():
    assert somar(-1, -1) == -2

Muito mais simples, não é? O pytest encontra automaticamente todos os arquivos que começam com test_ e executa as funções de teste. Você pode rodar os testes com um comando simples no terminal:

pytest

Primeiros Passos: Criando Seu Primeiro Teste

Vamos criar um exemplo prático completo do zero. Imagine que você está desenvolvendo um sistema de gerenciamento de tarefas e precisa testar uma função que calcula quantos dias faltam para o prazo de uma tarefa.

Primeiro, crie um arquivo chamado tarefas.py com a função que queremos testar:

Python
from datetime import datetime, timedelta

def dias_ate_prazo(data_prazo):
    """
    Calcula quantos dias faltam até o prazo.
    
    Args:
        data_prazo: String no formato 'YYYY-MM-DD'
    
    Returns:
        Número de dias (int)
    """
    hoje = datetime.now().date()
    prazo = datetime.strptime(data_prazo, '%Y-%m-%d').date()
    diferenca = prazo - hoje
    return diferenca.days

Agora crie um arquivo de testes chamado test_tarefas.py. É importante que o nome comece com test_ para que o pytest encontre automaticamente:

Python
from datetime import datetime, timedelta
from tarefas import dias_ate_prazo

def test_prazo_futuro():
    """Testa quando o prazo está no futuro"""
    amanha = datetime.now() + timedelta(days=1)
    data_str = amanha.strftime('%Y-%m-%d')
    assert dias_ate_prazo(data_str) == 1

def test_prazo_hoje():
    """Testa quando o prazo é hoje"""
    hoje = datetime.now().strftime('%Y-%m-%d')
    assert dias_ate_prazo(hoje) == 0

def test_prazo_passado():
    """Testa quando o prazo já passou"""
    ontem = datetime.now() - timedelta(days=1)
    data_str = ontem.strftime('%Y-%m-%d')
    assert dias_ate_prazo(data_str) == -1

Execute os testes com o comando pytest no terminal. Você verá uma saída mostrando quantos testes passaram e se algum falhou. O pytest usa cores para facilitar a visualização: verde para testes que passaram, vermelho para os que falharam.

Estruturando Seus Testes de Forma Profissional

Conforme seu projeto cresce, organizar os testes se torna fundamental. Uma boa estrutura ajuda você e sua equipe a encontrar e manter os testes facilmente. Veja uma organização recomendada para projetos Python:

meu_projeto/
├── src/
│   ├── __init__.py
│   ├── calculadora.py
│   ├── validador.py
│   └── processador.py
├── tests/
│   ├── __init__.py
│   ├── test_calculadora.py
│   ├── test_validador.py
│   └── test_processador.py
├── pytest.ini
└── requirements.txt

Separe seu código principal na pasta src/ e todos os testes na pasta tests/. Para cada arquivo de código, crie um arquivo de teste correspondente. Se você tem calculadora.py, crie test_calculadora.py. Essa convenção facilita muito encontrar os testes relacionados a cada parte do código.

O arquivo pytest.ini na raiz do projeto permite configurar como o pytest se comporta. Aqui está um exemplo de configuração útil:

[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short

Essas configurações dizem ao pytest para procurar testes na pasta tests/, mostrar informações detalhadas durante a execução (-v) e exibir mensagens de erro resumidas (--tb=short).

Métodos Assert Mais Utilizados

Os métodos assert são o coração dos testes unitários. Eles verificam se o comportamento do código está correto. No unittest, você tem vários métodos disponíveis. Vamos conhecer os mais importantes:

MétodoO Que VerificaExemplo
assertEqual(a, b)Se a é igual a bself.assertEqual(2+2, 4)
assertNotEqual(a, b)Se a é diferente de bself.assertNotEqual(1, 2)
assertTrue(x)Se x é verdadeiroself.assertTrue(5 > 3)
assertFalse(x)Se x é falsoself.assertFalse(2 > 5)
assertIn(a, b)Se a está em bself.assertIn(‘a’, ‘casa’)
assertIsNone(x)Se x é Noneself.assertIsNone(variavel)
assertRaises(erro)Se uma exceção é lançadaself.assertRaises(ValueError)

Veja um exemplo prático usando diferentes métodos assert com unittest:

Python
import unittest

class TestValidador(unittest.TestCase):
    def test_validar_email(self):
        email = "usuario@exemplo.com"
        self.assertIn('@', email)
        self.assertTrue(email.endswith('.com'))
    
    def test_lista_vazia(self):
        lista = []
        self.assertEqual(len(lista), 0)
        self.assertFalse(lista)
    
    def test_divisao_por_zero(self):
        with self.assertRaises(ZeroDivisionError):
            resultado = 10 / 0

No pytest, você pode usar o assert simples do Python para a maioria dos casos. O pytest é inteligente o suficiente para mostrar informações detalhadas quando um teste falha, mesmo com asserts simples. Para verificar exceções no pytest, use pytest.raises:

Python
import pytest

def test_divisao_por_zero():
    with pytest.raises(ZeroDivisionError):
        resultado = 10 / 0

Testando Diferentes Cenários com Parametrização

Muitas vezes você precisa testar a mesma função com vários conjuntos de dados diferentes. Em vez de escrever um teste separado para cada caso, você pode usar parametrização. Isso torna seus testes mais limpos e fáceis de manter.

O pytest oferece o decorator @pytest.mark.parametrize para isso. Veja um exemplo testando uma função de validação de senha com múltiplos casos:

Python
import pytest

def senha_forte(senha):
    """Verifica se a senha tem pelo menos 8 caracteres"""
    return len(senha) >= 8

@pytest.mark.parametrize("senha,esperado", [
    ("abc123", False),
    ("senha123", True),
    ("12345678", True),
    ("curta", False),
    ("senhasupersegura", True),
])
def test_validacao_senha(senha, esperado):
    assert senha_forte(senha) == esperado

Esse único teste será executado cinco vezes, uma para cada combinação de senha e resultado esperado. Se algum caso falhar, o pytest mostra exatamente qual foi. Isso é muito mais eficiente do que escrever cinco funções de teste separadas.

Você também pode parametrizar múltiplos argumentos. Veja um exemplo com uma função de cálculo de desconto:

Python
@pytest.mark.parametrize("preco,percentual,esperado", [
    (100, 10, 90),
    (50, 20, 40),
    (200, 0, 200),
    (150, 50, 75),
])
def test_calcular_desconto(preco, percentual, esperado):
    resultado = aplicar_desconto(preco, percentual)
    assert resultado == esperado

Fixtures: Preparando o Ambiente de Teste

Fixtures são recursos que seus testes precisam para funcionar. Podem ser conexões com banco de dados, arquivos temporários, objetos complexos ou qualquer coisa que precise ser configurada antes dos testes rodarem. O pytest tem um sistema poderoso de fixtures.

Imagine que você está testando uma classe que gerencia uma lista de compras. Em vez de criar essa lista em cada teste, você cria uma fixture:

Python
import pytest

class ListaCompras:
    def __init__(self):
        self.itens = []
    
    def adicionar(self, item):
        self.itens.append(item)
    
    def remover(self, item):
        self.itens.remove(item)
    
    def total_itens(self):
        return len(self.itens)

@pytest.fixture
def lista_compras():
    """Cria uma lista de compras para os testes"""
    return ListaCompras()

def test_adicionar_item(lista_compras):
    lista_compras.adicionar("Arroz")
    assert lista_compras.total_itens() == 1
    assert "Arroz" in lista_compras.itens

def test_remover_item(lista_compras):
    lista_compras.adicionar("Feijão")
    lista_compras.adicionar("Arroz")
    lista_compras.remover("Feijão")
    assert lista_compras.total_itens() == 1

A fixture lista_compras é executada antes de cada teste que a solicita. Note que você simplesmente adiciona o nome da fixture como parâmetro na função de teste. O pytest cuida de tudo automaticamente.

Fixtures podem ter diferentes escopos. Por padrão, uma nova fixture é criada para cada teste. Mas você pode definir que uma fixture seja criada apenas uma vez por módulo ou por sessão:

Python
@pytest.fixture(scope="module")
def conexao_banco():
    """Conexão compartilhada entre todos os testes do módulo"""
    conexao = criar_conexao()
    yield conexao
    conexao.fechar()

O comando yield divide a fixture em duas partes: o que vem antes é a configuração, o que vem depois é a limpeza. Isso garante que recursos sejam liberados corretamente após os testes.

Mocks: Simulando Comportamentos Externos

Nem sempre você pode testar algo que depende de recursos externos. Por exemplo, você não quer que seus testes enviem emails reais ou façam chamadas para APIs pagas toda vez que rodam. É aí que entram os mocks.

Um mock é um objeto falso que simula o comportamento de algo real. Python tem a biblioteca unittest.mock para isso, que funciona tanto com unittest quanto com pytest. Veja um exemplo testando uma função que busca dados de uma API:

Python
from unittest.mock import Mock, patch
import requests

def buscar_usuario(user_id):
    """Busca dados de um usuário na API"""
    resposta = requests.get(f'https://api.exemplo.com/users/{user_id}')
    return resposta.json()

def test_buscar_usuario():
    # Cria um mock da resposta da API
    mock_resposta = Mock()
    mock_resposta.json.return_value = {
        'id': 1,
        'nome': 'João Silva',
        'email': 'joao@exemplo.com'
    }
    
    # Substitui requests.get pelo mock
    with patch('requests.get', return_value=mock_resposta):
        usuario = buscar_usuario(1)
        assert usuario['nome'] == 'João Silva'
        assert usuario['email'] == 'joao@exemplo.com'

O teste acima não faz nenhuma chamada real à API. O patch substitui temporariamente requests.get por um mock que retorna os dados que definimos. Isso torna o teste rápido, confiável e independente de conexão com internet.

Você também pode verificar se uma função foi chamada e com quais argumentos:

Python
def test_enviar_email():
    with patch('smtplib.SMTP') as mock_smtp:
        enviar_email('usuario@exemplo.com', 'Assunto', 'Corpo')
        
        # Verifica se o email foi enviado
        mock_smtp.assert_called_once()
        
        # Verifica os argumentos da chamada
        args, kwargs = mock_smtp.call_args
        assert 'usuario@exemplo.com' in str(args)

Medindo a Cobertura dos Testes

Cobertura de testes mostra qual porcentagem do seu código é executada pelos testes. Uma cobertura alta indica que você está testando bem seu código, mas não garante que os testes sejam bons. O ideal é ter pelo menos 80% de cobertura.

Para medir a cobertura no pytest, instale o plugin pytest-cov:

Bash
pip install pytest-cov

Execute os testes com o relatório de cobertura:

Bash
pytest --cov=src tests/

Você verá um relatório mostrando a porcentagem de cobertura de cada arquivo. Para um relatório mais detalhado em HTML, use:

Bash
pytest --cov=src --cov-report=html tests/

Isso cria uma pasta htmlcov/ com arquivos HTML. Abra htmlcov/index.html no navegador e você verá exatamente quais linhas do seu código não estão sendo testadas. As linhas verdes foram executadas, as vermelhas não foram.

Você pode adicionar a configuração de cobertura no pytest.ini:

[pytest]
addopts = --cov=src --cov-report=html --cov-report=term-missing
testpaths = tests

Com essa configuração, toda vez que você rodar pytest, o relatório de cobertura será gerado automaticamente. A opção --cov-report=term-missing mostra no terminal quais linhas não foram cobertas.

Boas Práticas em Testes Unitários

Seguir boas práticas faz a diferença entre testes que ajudam e testes que atrapalham. Um teste bem escrito é fácil de entender, rápido de executar e confiável nos resultados. Aqui estão as práticas mais importantes:

Teste apenas uma coisa por vez. Cada teste deve verificar um comportamento específico. Se um teste falha, você deve saber imediatamente qual parte do código tem problema. Evite testes que verificam múltiplas coisas não relacionadas.

Use nomes descritivos. O nome do teste deve dizer exatamente o que está sendo testado. Em vez de test_funcao1, use test_calcular_desconto_retorna_preco_reduzido. Quando um teste falhar, você entenderá o problema apenas lendo o nome.

Mantenha testes independentes. Um teste nunca deve depender de outro. A ordem de execução não deve importar. Cada teste deve configurar tudo que precisa e limpar depois, de preferência usando fixtures.

Testes devem ser rápidos. Testes unitários devem rodar em milissegundos. Se estão demorando segundos, provavelmente você está testando muita coisa junta ou fazendo operações pesadas. Use mocks para simular operações lentas como banco de dados ou requisições de rede.

Escreva testes antes ou junto com o código. A prática de TDD (Test-Driven Development) sugere escrever o teste antes da funcionalidade. Isso força você a pensar no design da função antes de implementá-la. Se você não pratica TDD completo, pelo menos escreva testes logo após implementar cada função.

Teste casos extremos. Não teste apenas o caminho feliz. Teste o que acontece com valores negativos, zero, strings vazias, None, listas vazias. Esses casos extremos são onde a maioria dos bugs aparece.

Python
def test_casos_extremos():
    # Testa valores limites
    assert calcular_idade(0) == 0
    assert calcular_idade(150) raises ValueError
    
    # Testa tipos inesperados
    with pytest.raises(TypeError):
        calcular_idade("abc")
    
    # Testa valores None
    with pytest.raises(ValueError):
        calcular_idade(None)

Integrando Testes no Fluxo de Desenvolvimento

Testes só são úteis se você os executa regularmente. A melhor prática é integrar os testes no seu fluxo de trabalho usando ferramentas de integração contínua (CI/CD). Sempre que alguém faz um commit no repositório, os testes rodam automaticamente.

Serviços como GitHub Actions, GitLab CI e Travis CI executam seus testes toda vez que você envia código. Veja um exemplo de configuração do GitHub Actions que roda testes em Python:

name: Testes

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Configurar Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.9'
    
    - name: Instalar dependências
      run: |
        pip install -r requirements.txt
        pip install pytest pytest-cov
    
    - name: Executar testes
      run: pytest --cov=src tests/

Salve esse arquivo como .github/workflows/tests.yml no seu repositório. Agora os testes rodam automaticamente a cada push ou pull request. Se algum teste falhar, o GitHub bloqueia o merge até você corrigir.

Durante o desenvolvimento local, configure seu editor para mostrar resultados dos testes. A maioria das IDEs modernas como VS Code e PyCharm tem plugins que executam testes automaticamente quando você salva um arquivo.

Erros Comuns ao Escrever Testes

Mesmo desenvolvedores experientes cometem alguns erros clássicos ao escrever testes. Conhecer esses problemas ajuda você a evitá-los desde o início.

Testar implementação em vez de comportamento. Seus testes devem verificar o que a função faz, não como ela faz. Se você mudar a implementação interna mas o comportamento continua o mesmo, os testes não devem quebrar.

Testes muito acoplados ao código. Se você precisa mudar três testes toda vez que muda uma linha de código, seus testes estão acoplados demais. Use abstrações e fixtures para reduzir esse acoplamento.

Ignorar testes que falham. Se um teste está falhando intermitentemente (às vezes passa, às vezes falha), não ignore. Esses “flaky tests” geralmente indicam problemas reais no código, como condições de corrida ou dependências de estado global.

Testar código de terceiros. Não escreva testes para bibliotecas que você usa. Se você está usando requests ou pandas, assuma que eles já foram testados. Teste apenas seu próprio código.

Testes muito grandes. Se um teste tem 50 linhas de código, provavelmente está testando coisa demais. Divida em testes menores. Cada teste deve ter no máximo 10-15 linhas, incluindo configuração e verificação.

Conclusão

Testes unitários são essenciais para desenvolver software de qualidade em Python. Eles permitem que você desenvolva com confiança, refatore código sem medo e identifique problemas rapidamente. Começar com testes simples usando unittest ou pytest é o primeiro passo.

A jornada com testes é contínua. Comece testando as partes mais críticas do seu código. Use parametrização para cobrir múltiplos cenários. Aproveite fixtures para organizar a configuração dos testes. E sempre meça a cobertura para saber onde adicionar mais testes.

Lembre-se: o objetivo não é ter 100% de cobertura, mas ter testes que realmente agregam valor. Um bom conjunto de testes é aquele que dá confiança para fazer mudanças e te avisa quando algo quebra. Continue praticando e seus testes vão melhorar naturalmente com o tempo.

Perguntas Frequentes (FAQ)

1. Qual a diferença entre unittest e pytest?

Unittest vem instalado com Python e usa classes, enquanto pytest precisa instalação mas tem sintaxe mais simples com funções.

2. Preciso testar 100% do código?

Não. Busque pelo menos 80% de cobertura focando nas partes críticas. Qualidade dos testes importa mais que quantidade.

3. Como testar funções que acessam banco de dados?

Use mocks para simular a conexão com banco ou crie um banco de teste temporário que é apagado após os testes.

4. Posso usar unittest e pytest juntos?

Sim, pytest executa testes escritos com unittest. Você pode migrar gradualmente para pytest mantendo testes antigos.

5. Quando devo escrever os testes?

O ideal é antes ou logo após implementar a função. Nunca deixe para depois, pois testes retroativos são mais difíceis.

6. Como testar funções com entrada do usuário?

Use mock para simular o input. Substitua a função input() por um mock que retorna valores predefinidos para cada teste.

7. O que são testes de integração?

Testes que verificam se múltiplas partes do sistema funcionam juntas. Diferentes de testes unitários que testam funções isoladas.

8. Como acelerar testes lentos?

Use mocks para operações externas, execute testes em paralelo com pytest-xdist, e use fixtures com escopo adequado.

9. Preciso testar funções privadas?

Geralmente não. Teste apenas a API pública. Funções privadas são testadas indiretamente através das funções públicas.

10. Como organizar testes em projetos grandes?

Espelhe a estrutura do código fonte na pasta de testes. Use conftest.py para compartilhar fixtures entre módulos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Logo do Python com o texto 'PEP 8' sobre fundo azul escuro, representando o guia de estilo da linguagem
    Boas Práticas
    Foto do Leandro Hirt

    Entenda o que é o PEP 8 e como Aplicá-lo no Python

    A linguagem Python é conhecida pela sua simplicidade e legibilidade. Desde o início, seu criador, Guido van Rossum, enfatizou que

    Ler mais

    Tempo de leitura: 9 minutos
    08/11/2025
    Pessoa programando em um notebook, vista de trás, com código desfocado exibido na tela em um fundo escuro
    Boas Práticas
    Foto do Leandro Hirt

    Como Documentar Código Python com Docstrings

    Documentar um código é uma das práticas mais importantes na programação. No Python, isso pode ser feito de forma simples

    Ler mais

    Tempo de leitura: 8 minutos
    07/11/2025
    2 balões de comentários em um fundo laranja
    FundamentosBoas Práticas
    Foto do Leandro Hirt

    Aprenda a Usar Comentários em Python

    Os comentários em Python são uma das ferramentas mais simples, mas também mais poderosas para quem está aprendendo a programar.

    Ler mais

    Tempo de leitura: 8 minutos
    26/10/2025

    Minicurso de Python

    Insira seu e-mail e para receber acesso às aulas agora mesmo