pytest-asyncio permite que o pytest execute corrotinas em um event loop controlado. O objetivo não é tornar a suíte paralela, mas testar código assíncrono sem chamar asyncio.run() manualmente em cada caso.
Primeiro teste assíncrono
python -m pip install pytest pytest-asyncio
import pytest
async def buscar_usuario(user_id: int) -> dict[str, object]:
return {"id": user_id, "ativo": True}
@pytest.mark.asyncio
async def test_buscar_usuario() -> None:
usuario = await buscar_usuario(7)
assert usuario == {"id": 7, "ativo": True}
Declare o modo no pyproject.toml ou na configuração do pytest. strict exige marcações explícitas; auto gerencia automaticamente funções async. Fixar a escolha evita uma suíte que muda de comportamento entre máquinas.
Recursos e limites
Fixtures assíncronas devem abrir e fechar clientes, conexões ou servidores no mesmo escopo. Evite compartilhar event loop e estado mutável sem necessidade. Para impedir espera infinita, teste a política de timeout da própria aplicação, não apenas envolva tudo com um prazo enorme.
Combine entradas com pytest parametrize e organize dependências com fixtures do pytest. Ao testar várias tarefas, revise também asyncio.TaskGroup para entender cancelamento e propagação de exceções.
Erros comuns
Configurar a descoberta explicitamente
Registre o modo no arquivo versionado para que execução local e CI concordem:
[tool.pytest.ini_options]
asyncio_mode = "strict"
O modo strict é uma boa escolha quando o repositório usa mais de um plugin assíncrono, pois o pytest-asyncio gerencia apenas testes e fixtures marcados. O modo auto reduz decoração num projeto dedicado a asyncio. Nenhum deles executa casos em paralelo; a opção define descoberta e responsabilidade pelo loop.
Se o pytest disser que pulou uma função async ou recebeu uma corrotina, confirme carregamento do plugin, marcador e localização da configuração. pytest --trace-config ajuda quando editor e CI descobrem plugins de maneiras diferentes.
Criar fixtures com ciclo de vida claro
No modo strict, use pytest_asyncio.fixture e coloque a limpeza depois de yield.
import pytest_asyncio
import httpx
@pytest_asyncio.fixture
async def cliente():
async with httpx.AsyncClient(base_url="https://example.test") as valor:
yield valor
Prefira escopo de função. Um escopo maior só melhora desempenho quando o recurso pode ser compartilhado e o escopo do loop é compatível. Conexões longas podem vazar transações, tarefas e estado entre casos. Se o setup for caro, restaure o estado explicitamente e documente quem encerra o recurso.
Testar exceções e cancelamento
Uma API async também é definida por suas falhas. Use pytest.raises ao redor da expressão aguardada:
@pytest.mark.asyncio
async def test_rejeita_usuario_desconhecido() -> None:
with pytest.raises(LookupError, match="usuário"):
await carregar_usuario(-1)
Em código concorrente, teste quais tarefas são canceladas quando uma irmã falha e confirme a limpeza. Não verifique ordem exata do scheduler, salvo se ela fizer parte do contrato. Use asyncio.Event para sincronizar, não um sleep curto que apenas torce para outra tarefa executar.
Cancelamento não é sucesso comum. Se o código suprime cancelamento, uma tarefa pode continuar depois que o chamador desistiu. Cancele a tarefa num teste focado, aguarde-a e verifique locks liberados, streams fechados e ausência de estado parcial.
Tornar timeouts determinísticos
Teste o mecanismo real da aplicação, como asyncio.timeout() ou a opção do cliente. Um fake pode esperar por um evento nunca sinalizado, acionando o limite sem serviço externo. Dê margem ao CI, mas injete um prazo curto na aplicação.
Não compare tempo decorrido em milissegundos. Verifique tipo de exceção, erro de domínio traduzido e limpeza. Em retries, injete função de espera ou relógio para a suíte não aguardar segundos reais. A meta é provar decisões, não medir o scheduler.
Detectar tarefas abandonadas
Um teste pode passar e deixar uma tarefa em background. Isso gera avisos no teardown e altera casos posteriores. Funções que criam tarefas precisam definir propriedade: aguardá-las, devolver um handle ou gerenciá-las num TaskGroup. O teste deve sinalizar encerramento e aguardar a conclusão.
Avisos de tarefa pendente são defeitos, não ruído. Execute com warnings visíveis e considere elevá-los a erro no CI. Feche geradores, clientes, pools e servidores por fixtures mesmo quando uma assertion falhar.
Isolar I/O na fronteira certa
Substitua transporte HTTP, repositório, relógio ou adapter de fila, não métodos internos do event loop. Um fake pequeno que implemente o protocolo async costuma ser mais claro que um mock profundo. Use AsyncMock quando chamadas importarem e forneça uma especificação para detectar métodos renomeados.
Mantenha poucos testes de integração com banco ou servidor local para verificar adapters. Eles ainda precisam de dados únicos e teardown. Não permita que testes unitários acessem a internet silenciosamente.
Parametrizar comportamento async
pytest.mark.parametrize funciona normalmente com testes async. Use-o para entradas-limite, status e mapeamento de erros. Dê ids descritivos, sobretudo quando os parâmetros forem dicionários.
Evite uma matriz gigantesca. Separe categorias para que a falha indique parsing, timeout, cancelamento ou persistência. Prefira resultados e efeitos visíveis; assertions sobre chamadas ficam para interações que realmente pertencem ao contrato.
Manter a suíte portátil
Checklist de revisão
Antes de integrar, execute o caso isolado várias vezes e depois a suíte completa. Confirme que nenhum teste depende da ordem, que warnings permanecem visíveis e que toda fixture encerra seus recursos quando há exceção. Rode também no Python mínimo e máximo suportado pelo projeto quando a automação permitir.
Observe o contrato público: resultados, exceções, cancelamento e efeitos persistidos. Um teste que examina tarefas internas demais ficará frágil após refatoração legítima. Dê nomes que descrevam cenário e resultado, mantenha arrange, act e assert reconhecíveis e prefira um motivo claro por caso.
Em revisão, procure time.sleep, clientes sem fechamento, tarefas criadas sem dono e acesso real à rede. Confirme ainda que o modo asyncio está no arquivo versionado e que as versões do plugin coincidem entre desenvolvimento e CI. Essa disciplina reduz testes intermitentes sem esconder falhas com repetição automática.
Loops e plataformas revelam suposições escondidas. Não dependa de estado global, ordem implícita ou porta escolhida manualmente. Deixe o sistema operacional escolher portas de servidores locais. Fixe versões compatíveis e leia notas de migração antes de mudar padrões de escopo.
Rode primeiro o arquivo focado e depois a suíte inteira para revelar vazamento de estado. Se a falha ocorrer apenas no CI, examine warnings, versões, raiz da configuração e tarefas pendentes antes de adicionar retries. Repetir uma corrida apenas esconde o defeito.
Não use time.sleep() dentro de corrotinas; ele bloqueia o loop. Prefira dependências simuladas ou asyncio.sleep() quando o atraso fizer parte do comportamento. Não faça chamadas externas reais em testes unitários e verifique efeitos observáveis, não detalhes internos do scheduler.
A documentação oficial do pytest-asyncio, consultada em 22 de julho de 2026, descreve modos, marcadores, fixtures e escopos de loop. Confirme a documentação da versão instalada, pois políticas de loop evoluem entre versões.