Testes com pytest: fixtures, markers e plugins
1. Introdução ao pytest e sua filosofia
O pytest se destaca como um dos frameworks de teste mais populares do ecossistema Python. Diferentemente do unittest, que exige classes herdadas de TestCase e métodos específicos, o pytest adota uma abordagem mais funcional e concisa. Sua filosofia central é permitir que você escreva testes simples com código Python puro, sem cerimônias.
Para instalar o pytest, basta um comando:
pip install pytest
A estrutura básica de um teste é surpreendentemente simples:
# test_exemplo.py
def test_soma():
assert 1 + 1 == 2
def test_string():
assert "python".upper() == "PYTHON"
O pytest descobre automaticamente arquivos com prefixo test_ ou sufixo _test.py, e funções com prefixo test_. Para executar, use:
pytest
2. Fixtures: o coração do pytest
Fixtures são funções que fornecem dados ou estado para os testes, gerenciando setup e teardown de forma elegante. Elas substituem os métodos setUp e tearDown do unittest.
import pytest
@pytest.fixture
def usuario_padrao():
return {"nome": "João", "email": "joao@email.com"}
def test_criar_usuario(usuario_padrao):
assert usuario_padrao["nome"] == "João"
O escopo das fixtures define sua vida útil:
function(padrão): recriada para cada testeclass: uma vez por classe de testemodule: uma vez por módulosession: uma vez por sessão de teste
@pytest.fixture(scope="module")
def conexao_banco():
print("\nConectando ao banco...")
conexao = {"status": "conectado"}
yield conexao
print("\nDesconectando do banco...")
O uso de yield permite executar código de limpeza após o teste, funcionando como um teardown automático.
3. Compartilhamento e modularização de fixtures
Para compartilhar fixtures entre vários arquivos de teste, use o arquivo conftest.py. Ele é automaticamente descoberto pelo pytest e suas fixtures ficam disponíveis para todos os testes no diretório e subdiretórios.
# conftest.py
import pytest
@pytest.fixture
def dados_api():
return {"endpoint": "https://api.exemplo.com", "token": "abc123"}
Fixtures podem depender de outras fixtures:
@pytest.fixture
def cliente_api(dados_api):
return f"Cliente conectado a {dados_api['endpoint']}"
def test_conexao(cliente_api):
assert "Cliente conectado" in cliente_api
Para parametrizar fixtures, use o parâmetro params:
@pytest.fixture(params=[1, 2, 3])
def numero(request):
return request.param
def test_dobro(numero):
assert numero * 2 == numero * 2
4. Markers: organizando e filtrando testes
Markers permitem adicionar metadados aos testes, facilitando a execução seletiva. O pytest já inclui marcadores úteis:
import pytest
@pytest.mark.skip(reason="Funcionalidade ainda não implementada")
def test_futuro():
pass
@pytest.mark.xfail(reason="Bug conhecido, será corrigido na versão 2.0")
def test_bug_conhecido():
assert 1 / 0 == 0
Você também pode criar marcadores customizados. Primeiro, registre-os no pyproject.toml ou pytest.ini:
# pytest.ini
[pytest]
markers =
lento: Testes que demoram muito
integracao: Testes que dependem de serviços externos
Depois, use-os nos testes:
@pytest.mark.lento
@pytest.mark.integracao
def test_api_externa():
import time
time.sleep(5)
assert True
Para executar apenas testes com determinados marcadores:
pytest -m "lento" # Apenas testes lentos
pytest -m "not lento" # Exclui testes lentos
pytest -m "lento and integracao" # Combinação
5. Parametrização de testes com @pytest.mark.parametrize
A parametrização permite testar múltiplos cenários com o mesmo código de teste:
@pytest.mark.parametrize("entrada, esperado", [
(2, 4),
(3, 9),
(4, 16),
(0, 0),
(-2, 4),
])
def test_quadrado(entrada, esperado):
assert entrada ** 2 == esperado
Para tornar os relatórios mais legíveis, use IDs descritivos:
@pytest.mark.parametrize(
"senha, valida",
[
("abc123", True),
("12345", False),
("", False),
("senha_segura_2024", True),
],
ids=["válida", "curta", "vazia", "válida longa"]
)
def test_validar_senha(senha, valida):
assert (len(senha) >= 6) == valida
É possível combinar parametrização com fixtures:
@pytest.fixture
def banco_dados():
return {"itens": []}
@pytest.mark.parametrize("item, quantidade", [
("maçã", 3),
("banana", 5),
])
def test_adicionar_itens(banco_dados, item, quantidade):
for _ in range(quantidade):
banco_dados["itens"].append(item)
assert banco_dados["itens"].count(item) == quantidade
6. Plugins essenciais para produtividade
pytest-cov
Mede a cobertura de código dos seus testes:
pip install pytest-cov
pytest --cov=meu_modulo --cov-report=html
pytest-mock
Simplifica o mocking com uma fixture mocker:
pip install pytest-mock
def test_buscar_usuario(mocker):
mock_resposta = {"id": 1, "nome": "Maria"}
mocker.patch("requests.get", return_value=mock_resposta)
resultado = buscar_usuario(1)
assert resultado["nome"] == "Maria"
pytest-xdist
Executa testes em paralelo para acelerar o feedback:
pip install pytest-xdist
pytest -n auto # Usa todos os núcleos da CPU
7. Boas práticas e padrões avançados
O pytest oferece fixtures embutidas poderosas:
def test_arquivo_temporario(tmp_path):
arquivo = tmp_path / "dados.txt"
arquivo.write_text("conteúdo")
assert arquivo.read_text() == "conteúdo"
def test_variavel_ambiente(monkeypatch):
monkeypatch.setenv("DB_URL", "sqlite:///test.db")
assert obter_url_banco() == "sqlite:///test.db"
Para testes com banco de dados:
@pytest.fixture(scope="function")
def db_session():
from meu_app import criar_conexao, criar_tabelas
conexao = criar_conexao("sqlite:///:memory:")
criar_tabelas(conexao)
yield conexao
conexao.close()
def test_inserir_usuario(db_session):
db_session.executar("INSERT INTO usuarios VALUES (1, 'Ana')")
resultado = db_session.consultar("SELECT nome FROM usuarios WHERE id=1")
assert resultado[0][0] == "Ana"
Estrutura de pastas recomendada:
projeto/
├── src/
│ └── meu_modulo/
│ ├── __init__.py
│ ├── calculadora.py
│ └── banco.py
├── tests/
│ ├── conftest.py
│ ├── test_calculadora.py
│ └── test_banco.py
├── pyproject.toml
└── pytest.ini
Com essa estrutura, execute pytest tests/ para rodar todos os testes.
O pytest transforma a escrita de testes em uma experiência produtiva e agradável. Sua combinação de fixtures flexíveis, marcadores poderosos e um ecossistema rico de plugins o torna indispensável para projetos Python de qualquer tamanho.
Referências
- Documentação oficial do pytest — Guia completo sobre fixtures, markers, parametrização e plugins
- pytest-cov: Cobertura de código — Plugin para medição de cobertura com geração de relatórios HTML e XML
- pytest-mock: Mocking simplificado — Integração direta com unittest.mock através da fixture mocker
- pytest-xdist: Execução paralela — Distribuição de testes entre múltiplos workers para aceleração
- Real Python: Effective Python Testing With Pytest — Tutorial prático cobrindo fixtures, parametrização e boas práticas
- Test-Driven Development with pytest (O'Reilly) — Livro abrangente sobre TDD usando pytest em projetos reais