Você construiu um agente de IA — um programa que usa um LLM (large language model — o cérebro por trás do ChatGPT, Claude, Gemini) para chamar ferramentas de forma autônoma. Ele consulta bancos de dados, verifica permissões, envia e-mails. No seu terminal, funciona toda vez. Aí você abre o diretório tests/ e percebe: está vazio. Não porque você é preguiçoso, mas porque seu SDK (software development kit — a caixa de ferramentas que um framework te dá) veio com exatamente zero utilitários de teste.
Bem-vindo ao desenvolvimento de agentes em abril de 2026.
A lacuna (resumo rápido)
Cobrimos o panorama dos SDKs em detalhe no artigo de notícias complementar. A versão curta: a Anthropic entrega zero utilitários de teste, a OpenAI se recusa a exportar o FakeModel interno deles, e o evaluator do Google ADK tem um bug aberto que retorna 0.0 em matches corretos. Ninguém está te dando ferramentas para testar seus agentes.
O pensamento óbvio seguinte: "Vou só fazer assert na saída de texto do agente." Não faça. Um LLM reformula a resposta a cada execução. Temperature (configuração que controla o quão aleatória é a saída do modelo) em zero não te salva — versões diferentes do modelo, dias diferentes, fraseado diferente. Comparar texto é testar cara ou coroa.
O que não muda? A sequência de chamadas de ferramentas. Quando seu agente processa "verificar se o usuário pode enviar e-mail", ele chama lookup_user → check_permissions → send_email toda vez, independente de como ele formula a resposta. O padrão de ações é o contrato. Teste isso.
Este guia te dá um test harness funcional em ~60 linhas. Mocks, golden fixtures, divisão de CI — tudo que os SDKs deveriam ter entregado mas não entregaram.
A receita: testes comportamentais em ~60 linhas
Vamos construir um test harness — um pequeno framework que observa seu agente — que:
- Faz mock do LLM para que os testes rodem em milissegundos a custo zero
- Registra quais ferramentas o agente chama e em que ordem
- Faz assert na sequência, não nas palavras
Passo 1: Escolha sua estratégia de mock
Dois caminhos. Testes com mock-model substituem o LLM por um respondedor roteirizado — instantâneo, gratuito, determinístico. Smoke tests com modelo real chamam a API de verdade e gravam as respostas — realistas mas caros e instáveis. Você precisa dos dois. Mocks no CI (a cada pull request), modelo real num cron noturno.
Para mocking, Pydantic AI tem as melhores primitivas: TestModel (chama automaticamente toda ferramenta que o agente tem) e FunctionModel (você roteiriza a resposta exata). Se você não está usando Pydantic AI, vamos construir a mesma coisa do zero.
Passo 2: Construa o interceptor
Abordagem pura com pytest — sem dependência de framework, funciona com qualquer SDK:
# test_agent_tools.py
import json
from dataclasses import dataclass, field
@dataclass
class ToolCallRecord:
"""Uma invocação de ferramenta registrada."""
name: str
arguments: dict
order: int
@dataclass
class AgentRecorder:
"""Intercepta e registra cada chamada de ferramenta."""
calls: list[ToolCallRecord] = field(default_factory=list)
def record(self, tool_name: str, arguments: dict):
self.calls.append(ToolCallRecord(
name=tool_name,
arguments=arguments,
order=len(self.calls),
))
@property
def sequence(self) -> list[str]:
"""Só os nomes das ferramentas, em ordem."""
return [c.name for c in self.calls]
def assert_sequence(self, expected: list[str]):
assert self.sequence == expected, (
f"Expected {expected}, got {self.sequence}"
)
def assert_called_with(self, tool_name: str, **expected_args):
match = [c for c in self.calls if c.name == tool_name]
assert match, f"{tool_name} was never called"
assert match[0].arguments == expected_args
São 35 linhas. O recorder captura cada invocação de ferramenta e expõe duas asserções: assert_sequence (ferramentas certas, ordem certa?) e assert_called_with (argumentos certos?).
Passo 3: Conecte ao seu agente
Envolva suas funções de ferramenta para que o recorder veja cada chamada:
def make_tracked_tool(original_fn, recorder: AgentRecorder):
"""Envolve uma função de ferramenta para registrar chamadas antes de executar."""
async def tracked(*args, **kwargs):
recorder.record(original_fn.__name__, kwargs)
return await original_fn(*args, **kwargs)
return tracked
faion é um assistente CLI que gera scaffolding de código a partir de prompts estruturados — cole o bloco abaixo e ele vai produzir o recorder e o wrapper adaptados ao seu SDK.
/faion
Generate an AgentRecorder class and make_tracked_tool wrapper for behavioral testing of an AI agent.
Requirements:
- Pure Python, no framework dependency, dataclass-based
- AgentRecorder must record tool name, arguments, and call order
- Must expose assert_sequence(expected_names) and assert_called_with(tool_name, **kwargs)
- make_tracked_tool wraps any async tool function to record calls before executing
- Detect my agent SDK (anthropic / openai-agents / google-adk / pydantic-ai) from imports and adapt the wrapper signature accordingly
- Include a pytest fixture that returns a fresh AgentRecorder
Passo 4: Escreva seu primeiro teste comportamental
import pytest
@pytest.fixture
def recorder():
return AgentRecorder()
@pytest.mark.asyncio
async def test_email_permission_flow(recorder, mock_agent):
"""O agente DEVE verificar permissões antes de enviar e-mail."""
await mock_agent.run("Send the quarterly report to [email protected]")
# O CONTRATO: sempre verificar antes de enviar
recorder.assert_sequence([
"lookup_user",
"check_permissions",
"send_email",
])
# Verifica se o agente buscou o usuário correto
recorder.assert_called_with(
"lookup_user", email="[email protected]"
)
Esse teste não liga para o que o agente diz. Ele liga para o que o agente faz. O texto pode mudar a cada execução. A verificação de permissão não pode.
Passo 5: Teste caminhos de recuperação de erro
O artigo de notícias cobriu a tese do "caminho feliz". Guias ganham seu valor nos caminhos feios — o que acontece quando uma ferramenta dá erro, quando o modelo tenta de novo, quando ele alucina uma ferramenta que não existe.
@pytest.mark.asyncio
async def test_graceful_degradation_on_tool_failure(recorder, mock_agent):
"""Quando send_email falha, o agente NÃO PODE tentar de novo sem re-verificar permissões."""
mock_agent.configure_tool_failure("send_email", error="SMTP timeout")
await mock_agent.run("Send the quarterly report to [email protected]")
recorder.assert_sequence([
"lookup_user",
"check_permissions",
"send_email", # falha
"check_permissions", # deve re-verificar antes do retry
"send_email", # retry
])
@pytest.mark.asyncio
async def test_unknown_tool_call_is_caught(recorder, mock_agent):
"""Se o modelo alucina um nome de ferramenta, o harness captura."""
mock_agent.inject_fake_tool_call("send_fax") # não existe
with pytest.raises(UnknownToolError):
await mock_agent.run("Fax the report")
# O agente não deveria ter chamado nada com sucesso
assert recorder.sequence == []
Recuperação de erro é onde agentes quebram silenciosamente em produção. Um mock que simula falhas de ferramentas custa zero e pega as falhas que seus testes de caminho feliz nunca vão pegar.
/faion
Scaffold a full behavioral test suite for my AI agent project.
Requirements:
- Scan my agent's registered tools and generate one test per tool
- For each tool: happy-path test + error-recovery test (tool raises exception)
- Generate a mock agent fixture that uses scripted responses instead of real LLM calls
- Include conftest.py with AgentRecorder fixture and make_tracked_tool helper
- Add golden fixture save/load utilities (JSON snapshots in tests/fixtures/)
- Mark real-model tests with @pytest.mark.smoke
- Detect my SDK from imports and adapt accordingly
Passo 6: Adicione golden fixtures para detecção de regressão
Um golden fixture é um snapshot do comportamento esperado que você armazena no controle de versão — sua baseline abençoada. Salve a saída do seu recorder como JSON:
# conftest.py
import json
from pathlib import Path
FIXTURE_DIR = Path("tests/fixtures")
def save_golden(recorder: AgentRecorder, name: str):
"""Salva a sequência atual de tool-calls como baseline abençoada."""
fixture = {
"sequence": recorder.sequence,
"calls": [
{"name": c.name, "arguments": c.arguments}
for c in recorder.calls
],
}
(FIXTURE_DIR / f"{name}.json").write_text(json.dumps(fixture, indent=2))
def assert_matches_golden(recorder: AgentRecorder, name: str):
"""Asserta que o comportamento atual bate com a baseline salva."""
fixture = json.loads((FIXTURE_DIR / f"{name}.json").read_text())
assert recorder.sequence == fixture["sequence"], (
f"Desvio comportamental detectado!\n"
f" Golden: {fixture['sequence']}\n"
f" Atual: {recorder.sequence}"
)
Quando você atualizar seu modelo do Claude Sonnet 4 para o que vier depois, rode a suite. Se o agente começar a pular check_permissions, você vai saber antes dos seus usuários.
Passo 7: Integração com CI
Sua configuração de CI se divide em dois jobs. A pirâmide de testes da Block Engineering acerta em cheio o princípio: "Não rodamos testes com LLM real no CI. É muito caro, muito lento e muito instável."
# .github/workflows/agent-tests.yml
name: Agent Tests
on: [pull_request]
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pytest tests/agents/ -m "not smoke" --tb=short
# .github/workflows/agent-smoke.yml
name: Agent Smoke Tests (Nightly)
on:
schedule:
- cron: '0 6 * * *' # 2am ET
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pytest tests/agents/ -m smoke --tb=long
Marque testes com modelo real com @pytest.mark.smoke. Eles rodam de noite, alertam sobre desvios e nunca bloqueiam um PR.
/faion
Generate GitHub Actions CI config for agent behavioral tests.
Requirements:
- Two workflow files: agent-tests.yml (on PR) and agent-smoke.yml (nightly cron at 2am ET)
- PR workflow runs pytest with -m "not smoke", fails fast
- Nightly workflow runs pytest with -m smoke, uses secrets for API keys
- Add a third workflow: agent-golden-update.yml (manual trigger) that runs tests with --update-golden flag and opens a PR with fixture diffs
- Include pip caching and Python 3.12 setup
Pegadinhas
1. A Anthropic diz para não fazer isso. O blog de engenharia deles (9 de janeiro de 2026) avisa explicitamente: "Verificar se agentes seguiram passos muito específicos como uma sequência de chamadas de ferramentas resulta em testes excessivamente frágeis." Eles estão certos — para fluxos exploratórios com múltiplos passos onde vários caminhos válidos existem. Eles estão errados para caminhos críticos de negócio onde pular um passo significa uma brecha de segurança. Use asserções de sequência para contratos (verificações de permissão, validação de dados), asserções de resultado para todo o resto.
2. Golden fixtures ficam desatualizados. Atualize seu modelo e todo fixture quebra — não porque o agente está errado, mas porque ele encontrou um caminho legitimamente melhor. Solução: revise os diffs como code reviews. Se a nova sequência é válida, atualize o fixture. Se não é, você acabou de pegar uma regressão.
3. Testes com mock mentem para você. Um FakeModel que sempre chama check_permissions te diz que sua lógica de roteamento funciona. Não te diz nada sobre se o Claude ou GPT vai realmente invocar aquela ferramenta. Testes com mock cobrem aproximadamente 70% da superfície testável — roteamento, parsing de argumentos, tratamento de erros. Os 30% restantes precisam de smoke tests com modelo real num schedule noturno.
4. Não-determinismo é estrutural, não é bug. Mesmo com a mesma entrada, um modelo pode chamar ferramentas em ordem diferente entre execuções. Para caminhos não-críticos, use AgentEvals com modo de trajetória unordered — ele verifica o conjunto de ferramentas chamadas, não a sequência. Reserve ordenação estrita para caminhos onde a ordem É o contrato.
5. Teste handoffs multi-agente separadamente. Se seu agente delega para sub-agentes, cada handoff é uma fronteira. Registre a chamada de delegação como uma chamada de ferramenta (delegate_to_research_agent), depois teste a sequência de ferramentas do sub-agente independentemente. Cruzar fronteiras de agentes numa única asserção torna tudo frágil e nada debugável.
O que você pode fazer agora
Você tem um diretório tests/agents/ com testes comportamentais, golden fixtures no controle de versão e um CI dividido que roda mocks rápidos em cada PR e smoke tests com modelo real toda noite. Seu agente vai para produção com a mesma confiança que o resto da sua codebase — não porque ele virou determinístico, mas porque você parou de testar a coisa errada. O texto muda a cada execução. As ações não deveriam.





