🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

Como escrever testes de unidade em Python com pytest: guia completo 2026

⏱️5 min read  ·  1,047 words

pytest é o padrão de fato para testar código Python em 2026 – mais simples e mais poderoso do que o unittest integrado. Bons testes detectam bugs antes da produção, permitem refatoração confiável e documentam como seu código deve se comportar. Este guia leva você desde o primeiro teste até padrões avançados.

Configuração

pip install pytest pytest-cov

# Project structure
myproject/
├── src/
│   └── calculator.py
├── tests/
│   └── test_calculator.py
└── pytest.ini

Seu primeiro teste

# src/calculator.py
def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b
# tests/test_calculator.py
from src.calculator import add, divide
import pytest

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

def test_divide():
    assert divide(10, 2) == 5

def test_divide_by_zero_raises():
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(10, 0)
# Run the tests
pytest                    # run all tests
pytest -v                 # verbose (shows each test name)
pytest tests/test_calculator.py::test_add   # run one test
pytest -k "divide"        # run tests matching "divide"

O padrão AAA (organizar, agir, afirmar)

def test_user_discount():
    # Arrange — set up test data
    user = User(name="Alice", is_premium=True)
    cart = Cart(total=100)

    # Act — call the code under test
    final_price = apply_discount(cart, user)

    # Assert — verify the result
    assert final_price == 80  # 20% premium discount

Fixtures — Configuração de teste reutilizável

import pytest

@pytest.fixture
def sample_user():
    # Provides a fresh user for each test that needs one
    return User(name="Alice", email="alice@example.com")

@pytest.fixture
def db_connection():
    # Setup and teardown - code after yield runs as cleanup
    conn = create_test_db()
    yield conn          # test runs here
    conn.close()        # cleanup after test

# Tests receive fixtures as arguments
def test_user_email(sample_user):
    assert sample_user.email == "alice@example.com"

def test_save_user(sample_user, db_connection):
    db_connection.save(sample_user)
    assert db_connection.count() == 1

Parametrização — Teste muitos casos

import pytest

@pytest.mark.parametrize("a, b, expected", [
    (2, 3, 5),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
])
def test_add_many_cases(a, b, expected):
    assert add(a, b) == expected

# Runs 4 separate tests, one per tuple — clear failure reporting per case

Zombaria — Isole o código das dependências

from unittest.mock import patch, MagicMock

# Mock an external API call so tests don't hit the network
def test_fetch_user_data():
    with patch('src.api.requests.get') as mock_get:
        mock_get.return_value.json.return_value = {"id": 1, "name": "Alice"}
        mock_get.return_value.status_code = 200

        result = fetch_user_data(1)

        assert result["name"] == "Alice"
        mock_get.assert_called_once_with("https://api.example.com/users/1")

# Mock with pytest-mock (cleaner)
def test_with_mocker(mocker):
    mock_db = mocker.patch('src.service.database')
    mock_db.get_user.return_value = {"name": "Bob"}

    result = get_user_name(1)
    assert result == "Bob"

Testando exceções e casos extremos

def test_raises_on_invalid_input():
    with pytest.raises(ValueError) as exc_info:
        validate_age(-5)
    assert "must be positive" in str(exc_info.value)

def test_edge_cases():
    assert process([]) == []           # empty input
    assert process([1]) == [1]         # single item
    assert process(None) is None       # None handling

Medindo a cobertura

# Run tests with coverage report
pytest --cov=src --cov-report=term-missing

# Output shows which lines aren't tested:
# Name                 Stmts   Miss  Cover   Missing
# src/calculator.py       10      1    90%    15

# Generate an HTML report
pytest --cov=src --cov-report=html
# Open htmlcov/index.html to see line-by-line coverage

Configuração (pytest.ini)

# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = -v --cov=src --cov-report=term-missing
markers =
    slow: marks tests as slow (deselect with '-m "not slow"')
    integration: integration tests

Melhores Práticas

  • Um conceito de afirmação por teste: Cada teste verifica um comportamento — as falhas apontam diretamente para o problema
  • Nomes descritivos: test_divide_by_zero_raises_valueerror conta o que quebrou sem ler o código
  • Comportamento de teste, não implementação: Teste o que a função faz, não como — para que a refatoração não interrompa os testes
  • Mantenha os testes rápidos e isolados: Cada teste deve ser executado de forma independente em qualquer ordem
  • Simulação de dependências externas: Bancos de dados, APIs e sistemas de arquivos devem ser ridicularizados em testes unitários
  • Busque uma cobertura significativa: 80%+ é uma boa meta, mas cubra completamente os caminhos críticos em vez de perseguir 100%

Perguntas Frequentes

P: pytest vs unittest?
R: pytest é mais simples (simplesassert, sem classes padronizadas), mais poderoso (fixtures, parametrização) e possui um rico ecossistema de plugins. Ele também pode executar testes unittest. Use pytest para novos projetos.

P: De quanta cobertura de teste eu preciso?
R: 80% é uma meta comum. Mas a cobertura é um guia, não uma meta — 100% de cobertura com afirmações fracas é pior que 80% com testes completos de lógica crítica. Concentre-se em cobrir o código que importa.

P: Devo simular o banco de dados ou usar um banco de dados de teste?
R: Simule em testes unitários (rápidos, isolados). Use um banco de dados de teste real em testes de integração (verifica o funcionamento das consultas reais). Ambos têm seu lugar – testes unitários para lógica, testes de integração para a camada de dados.

P: Qual é a diferença entre um fixture e uma função regular?
R: As luminárias são gerenciadas pelo pytest — elas são configuradas antes dos testes que as solicitam e podem ser limpas depois (via rendimento). Eles fornecem dependências de teste isoladas e reutilizáveis com gerenciamento automático do ciclo de vida.

P: Como faço para testar o código assíncrono?
R: Instalepytest-asyncio e marque os testes com@pytest.mark.asyncio. Então você podeawait dentro de suas funções de teste para testar funções assíncronas.

Conclusão

pytest torna os testes em Python simples e poderosos. Comece com simplesassert instruções no padrão AAA, use fixtures para configuração reutilizável, parametrize para cobrir muitos casos de forma concisa e simule dependências externas para manter os testes rápidos e isolados. Meça a cobertura para encontrar código não testado, mas priorize o teste completo da lógica crítica em vez de buscar 100%. Bons testes são um investimento que compensa sempre que você refatora ou adiciona recursos com confiança.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা