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.
📋 Table of Contents
- Configuração
- Seu primeiro teste
- O padrão AAA (organizar, agir, afirmar)
- Fixtures — Configuração de teste reutilizável
- Parametrização — Teste muitos casos
- Zombaria — Isole o código das dependências
- Testando exceções e casos extremos
- Medindo a cobertura
- Configuração (pytest.ini)
- Melhores Práticas
- Perguntas Frequentes
- Conclusão
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_valueerrorconta 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.
🔗 Share this article
✍️ Leave a Comment