Fixtures do pytest fornecem dados e recursos aos testes sem repetir preparação em cada função. Mocks e monkeypatch controlam dependências externas, como relógio, ambiente ou HTTP. Usados com moderação, tornam os testes determinísticos; usados em excesso, validam uma simulação distante do sistema real.
Este conteúdo aprofunda o guia de testes automatizados com pytest.
Criar uma fixture com limpeza
import pytest
@pytest.fixture
def repositorio(tmp_path):
arquivo = tmp_path / "usuarios.json"
arquivo.write_text("[]", encoding="utf-8")
yield arquivo
# Recursos externos poderiam ser fechados aqui.
def test_repositorio_inicia_vazio(repositorio):
assert repositorio.read_text(encoding="utf-8") == "[]"
O nome do parâmetro solicita a fixture. O código antes de yield prepara o recurso; o código depois executa a limpeza mesmo quando o teste falha. tmp_path já fornece um diretório exclusivo por teste.
Escopo function é o padrão e oferece maior isolamento. Use module ou session apenas para recursos caros e seguros para compartilhamento. Estado mutável compartilhado cria testes dependentes de ordem.
Controlar variáveis com monkeypatch
Considere uma função que lê configuração:
import os
def modo_atual() -> str:
return os.getenv("APP_MODE", "development")
O teste não deve depender do ambiente da máquina:
def test_modo_producao(monkeypatch):
monkeypatch.setenv("APP_MODE", "production")
assert modo_atual() == "production"
O pytest desfaz a alteração ao final. A documentação oficial de monkeypatch cobre atributos, dicionários, ambiente e diretório atual.
Simular uma chamada HTTP
Substitua no local onde a função é consultada:
class RespostaFalsa:
def json(self):
return {"status": "ok"}
def test_consultar_status(monkeypatch):
monkeypatch.setattr("app.cliente.requests.get", lambda *a, **k: RespostaFalsa())
assert consultar_status()["status"] == "ok"
O teste deve afirmar o comportamento da sua função, não detalhes irrelevantes do mock. Mantenha também testes de integração para confirmar contrato, timeout e erros reais da biblioteca de requisições HTTP.
Compor fixtures pequenas
Uma fixture pode solicitar outra. Isso deixa cada recurso compreensível:
import json
@pytest.fixture
def armazenamento_vazio(tmp_path):
caminho = tmp_path / "usuarios.json"
caminho.write_text("[]", encoding="utf-8")
return caminho
@pytest.fixture
def armazenamento_preenchido(armazenamento_vazio):
usuarios = [{"id": 1, "nome": "Ada"}]
armazenamento_vazio.write_text(json.dumps(usuarios), encoding="utf-8")
return armazenamento_vazio
Retorne o valor quando não houver desmontagem. Use yield quando conexão ou transação precisar ser encerrada. Se a preparação falhar antes do yield, a desmontagem não acontece; isole aquisições arriscadas ou use gerenciador de contexto.
Escopos incluem function, class, module, package e session. Um escopo amplo economiza tempo, mas compartilha estado. Banco mutável com escopo de sessão costuma gerar falhas dependentes da ordem. Uma alternativa é compartilhar somente o engine e reverter uma transação por teste.
Parametrizar variações de dados
Quando mudam apenas entrada e resultado esperado, parametrização é mais clara:
@pytest.mark.parametrize(
("email", "valido"),
[("[email protected]", True), ("sem-arroba.example.com", False), ("", False)],
)
def test_validacao_email(email, valido):
assert email_valido(email) is valido
Dê id a casos complexos. Evite laço com várias asserções, pois a primeira falha esconde casos seguintes. Consulte o guia oficial de parametrização.
Substituir onde o nome é consultado
Se app.cliente faz from requests import get, altere app.cliente.get, não requests.get. Imports criam referências locais. Mantenha raising=True, padrão de setattr, para atributo errado falhar imediatamente. Use setitem para dicionários e setenv para ambiente.
def test_diretorio_config(monkeypatch, tmp_path):
with monkeypatch.context() as alteracao:
alteracao.setattr(Path, "home", lambda: tmp_path)
assert caminho_config() == tmp_path / ".meuapp.toml"
O pytest desfaz tudo mesmo após falha, mais confiável que restauração manual.
Usar Mock quando a interação é o contrato
from unittest.mock import Mock
def test_notifica_depois_de_salvar():
repositorio = Mock()
notificador = Mock()
servico = ServicoUsuario(repositorio, notificador)
servico.criar("[email protected]")
repositorio.salvar.assert_called_once()
notificador.enviar.assert_called_once_with("[email protected]")
Use spec ou autospec para detectar atributos inexistentes. Não verifique toda chamada interna: isso quebra com refatorações inofensivas. Um fake pequeno frequentemente é melhor para repositórios e filas. Para funções assíncronas, use AsyncMock; configure side_effect para exceções ou respostas sequenciais.
Controlar relógio e aleatoriedade
Injete uma função em vez de alterar vários detalhes internos:
from datetime import datetime, timezone
def criar_registro(agora=lambda: datetime.now(timezone.utc)):
return {"criado_em": agora().isoformat()}
def test_registro_usa_horario_atual():
fixo = datetime(2026, 1, 2, tzinfo=timezone.utc)
assert criar_registro(lambda: fixo)["criado_em"] == "2026-01-02T00:00:00+00:00"
Aplique a ideia a UUID, aleatoriedade e clientes de rede.
Manter conftest.py previsível
O pytest descobre conftest.py pela hierarquia. Infraestrutura geral pode ficar na raiz; fábricas específicas ficam perto da funcionalidade. Fixtures autouse merecem cautela porque alteram testes sem aparecer nos parâmetros. Servem para proteção universal, como impedir rede acidental, mas estado escondido dificulta diagnóstico.
Teste unitário rápido não exige substituir tudo. Funções puras, diretórios temporários, fakes e servidores locais exercitam mais comportamento. Testes de integração confirmam serialização, banco, HTTP e wiring. Uma suíte sustentável tem preparação, ação e verificação claras, testes independentes e nenhuma dependência de ordem.
Organização sustentável
Fixtures compartilhadas podem ficar em conftest.py, sem importação manual. Evite transformar esse arquivo em um depósito global; mantenha fixtures perto dos testes que as usam e dê nomes que expressem o recurso fornecido.
- Prefira fixtures pequenas e compostas.
- Use
tmp_pathem vez de arquivos fixos no repositório. - Teste sucesso, timeout, resposta inválida e exceção.
- Não faça chamadas de rede em testes unitários.
- Preserve alguns testes de integração para detectar contratos falsos.
A referência de fixtures lista recursos integrados. O melhor teste continua sendo aquele que falha por uma mudança de comportamento importante, não por detalhes internos sem relevância para o usuário.
Diagnosticar falhas sistematicamente
Execute primeiro o menor teste com falha, depois o módulo e finalmente a suíte. Um teste que passa sozinho e falha em conjunto costuma revelar estado global vazado, recurso aberto, fixture reutilizada ou dependência de ordem. Inspecione escopo e desmontagem antes de adicionar repetição automática. Repetir pode ajudar ao observar serviço externo, mas não deve esconder unidade não determinística.
Quando uma asserção de mock falha, veja os argumentos reais e pergunte se a expectativa representa contrato público. Normalizar um e-mail antes do envio pode ser intencional. Por outro lado, mock aprovado não prova que a assinatura de uma biblioteca externa continua igual, salvo quando spec ou integração a verifica.
Use pytest -k para selecionar cenários e pytest -x quando a primeira falha é a mais informativa. A saída detalhada identifica parâmetros, enquanto logs capturados explicam fronteiras. Não dependa de variável local ou arquivo existente apenas na máquina do desenvolvedor.
Cobrir erros e limites
Para HTTP, teste timeout, conexão recusada, status inesperado, JSON inválido e resposta válida incompleta. Configure a simulação para reproduzir cada fronteira e confirme o comportamento recebido pelo usuário, não somente a chamada feita ao mock.
Para banco, verifique restrições, commit e rollback. Um repositório fake valida regras do serviço, mas somente o banco real confirma índice único, tipo de coluna e isolamento da transação. Divida responsabilidades: testes unitários cobrem decisões rapidamente; integração cobre adaptadores; ponta a ponta cobre poucos trajetos essenciais.
Teste valores na fronteira, como lista vazia, quantidade zero, data no limite e texto Unicode. Não crie casos arbitrários apenas para aumentar números. Cada cenário deve representar risco ou regra.
Criar dados de teste legíveis
Fixtures de fábrica ajudam a fornecer um objeto válido e sobrescrever somente o campo relevante. Isso evita dicionários enormes copiados em dezenas de arquivos. Mesmo assim, não esconda campos obrigatórios importantes atrás de padrões mágicos.
Dê nomes que expressem a situação, como usuario_inativo ou pedido_pago. O corpo do teste deve deixar clara a diferença que provoca o resultado. Dados realistas não precisam conter informações pessoais reais; use exemplos inventados e seguros.
Quando a fixture persiste registros, devolva o objeto criado para a etapa de verificação. Limpe pelo mecanismo transacional quando possível. Apagar manualmente por identificadores fixos é frágil e pode remover dados de outro cenário.
Revisar a qualidade da suíte
Cobertura mede linhas executadas, não a força das asserções. Procure caminhos de erro, limites e regras de negócio cujo rompimento teria impacto. Um teste deve ter um motivo principal para falhar. Nomes como test_rejeita_email_duplicado informam mais que test_usuario_2.
Remova mocks obsoletos durante refatorações. Se uma colaboração desapareceu, conservar sua expectativa congela a arquitetura antiga. Ao mesmo tempo, não modifique teste apenas para deixá-lo verde: confirme se o comportamento esperado mudou deliberadamente.
Comentários devem explicar decisões incomuns, e não repetir código. Preparação, ação e verificação visíveis facilitam revisão. Falhas precisam apontar o cenário, por isso parametrizações complexas merecem identificadores legíveis.
Integrar a suíte ao fluxo
Execute testes rápidos a cada alteração e a suíte completa antes de integrar. Separe testes externos mais lentos com marcadores claros, sem deixar de executá-los no ambiente apropriado. Uma falha intermitente deve ser investigada e registrada, não aceita como normal.
Fixe versões de dependências de teste quando o projeto exigir reprodutibilidade. Atualize-as de forma controlada e leia mudanças de comportamento do pytest. Warnings podem antecipar quebras futuras e merecem correção.
Uma boa suíte funciona localmente e na integração contínua porque controla tempo, arquivos, ambiente e rede. Ela serve como documentação executável: explica o que o sistema promete e permite refatorar com confiança sem transformar cada detalhe interno em contrato.