Como usar IA para escrever testes que você não escreveria sozinho
1. O problema clássico: testes que a gente adia (e por quê)
Em qualquer projeto de software, existe uma categoria de testes que todo desenvolvedor reconhece como necessária, mas que raramente é escrita por iniciativa própria. São os testes de integração complexos, que exigem simular múltiplos serviços e dependências externas — como um microsserviço de pagamento que depende de três APIs de terceiros e um banco legado. Testes de borda e corner cases também entram nessa lista: situações improváveis, como um usuário que envia um payload de 10 MB em um campo de CPF, ou uma transação bancária que sofre rollback no exato momento em que o saldo é debitado em um servidor e creditado em outro. Por fim, há os testes de regressão em bases de dados legadas, onde schemas com colunas obsoletas, índices quebrados e dados imprevisíveis tornam a cobertura manual inviável.
A razão para adiar esses testes é simples: eles exigem criatividade para imaginar cenários, paciência para configurar dependências e energia mental para escrever asserções que realmente validem o comportamento esperado. É exatamente nesse ponto que a IA se torna uma aliada estratégica.
2. Por que a IA é ideal para preencher essas lacunas
A IA generativa, especialmente modelos treinados em grandes volumes de código e documentação, possui três características que a tornam perfeita para escrever testes que humanos evitam:
-
Geração de variações ilimitadas: a partir de uma descrição vaga como "testar login com senha inválida", a IA pode gerar dezenas de variações — senha vazia, senha com caracteres especiais, senha com 1000 caracteres, senha com Unicode, senha que excede o limite do campo no frontend mas não no backend.
-
Exploração sistemática de combinações: enquanto um humano tende a testar combinações óbvias (usuário existe + senha correta, usuário existe + senha errada), a IA pode gerar todas as combinações de parâmetros de uma função, incluindo aquelas que parecem absurdas mas revelam bugs reais.
-
Redução da fadiga mental: escrever o vigésimo teste de validação de campo de formulário é entediante. A IA não se cansa, não pula etapas e não assume que "ninguém faria isso".
3. Estratégias de prompt para testes de integração e dependências externas
Para gerar testes de integração complexos, o segredo está em fornecer à IA o contexto certo. Um prompt eficaz deve incluir a descrição da API, o schema esperado e exemplos de respostas reais ou mockadas.
Exemplo de prompt e saída:
Prompt: Gere um teste de integração em Python (pytest) para o endpoint POST /api/orders,
que depende de um serviço externo de pagamento (PaymentGateway) e de um banco de dados MySQL.
Inclua cenários de:
1. Timeout do PaymentGateway
2. Erro HTTP 500 do PaymentGateway
3. Dados corrompidos na resposta do PaymentGateway (JSON inválido)
4. Concorrência: duas requisições simultâneas para o mesmo pedido
Saída gerada pela IA:
import pytest
import requests
from unittest.mock import patch
from concurrent.futures import ThreadPoolExecutor
@pytest.fixture
def order_payload():
return {"user_id": 123, "items": [{"product_id": 1, "quantity": 2}], "total": 150.00}
def test_order_timeout(order_payload):
with patch('services.payment.PaymentGateway.charge') as mock_charge:
mock_charge.side_effect = requests.exceptions.Timeout
response = client.post("/api/orders", json=order_payload)
assert response.status_code == 504
assert "gateway_timeout" in response.json()["error"]
def test_order_payment_500(order_payload):
with patch('services.payment.PaymentGateway.charge') as mock_charge:
mock_charge.return_value = Mock(status_code=500, text="Internal Server Error")
response = client.post("/api/orders", json=order_payload)
assert response.status_code == 502
assert "payment_failed" in response.json()["error"]
def test_order_corrupted_response(order_payload):
with patch('services.payment.PaymentGateway.charge') as mock_charge:
mock_charge.return_value = Mock(status_code=200, text="not json at all")
response = client.post("/api/orders", json=order_payload)
assert response.status_code == 500
def test_concurrent_orders(order_payload):
with patch('services.payment.PaymentGateway.charge') as mock_charge:
mock_charge.return_value = Mock(status_code=200, json=lambda: {"status": "approved"})
with ThreadPoolExecutor(max_workers=2) as executor:
futures = [executor.submit(client.post, "/api/orders", json=order_payload) for _ in range(2)]
results = [f.result() for f in futures]
assert all(r.status_code == 201 for r in results)
Perceba como a IA gerou automaticamente os mocks, os status codes esperados e até a lógica de concorrência — algo que muitos desenvolvedores esqueceriam de testar.
4. Testes de propriedade e fuzzing: a IA como geradora de entradas inesperadas
Testes de propriedade (property-based testing) são ideais para verificar invariantes do sistema, mas exigem geradores de dados estruturados. A IA pode criar esses geradores a partir de uma simples descrição.
Exemplo de prompt e saída:
Prompt: Crie um gerador de dados para testar a propriedade "o saldo de uma conta bancária
nunca deve ficar negativo após qualquer sequência de operações de débito e crédito".
Use Hypothesis em Python.
Saída gerada pela IA:
from hypothesis import given, strategies as st
# Estratégia para gerar operações bancárias
operation_strategy = st.lists(
st.tuples(
st.sampled_from(["debit", "credit"]),
st.floats(min_value=0.01, max_value=10000.00, allow_nan=False, allow_infinity=False)
),
min_size=1,
max_size=100
)
@given(initial_balance=st.floats(min_value=0.0, max_value=100000.0), operations=operation_strategy)
def test_balance_never_negative(initial_balance, operations):
balance = initial_balance
for op_type, amount in operations:
if op_type == "debit":
balance -= amount
else:
balance += amount
# Propriedade: saldo nunca negativo
assert balance >= 0, f"Balance became negative: {balance} after operation {op_type} {amount}"
Esse teste gerado pela IA explora automaticamente milhares de combinações de valores iniciais e sequências de operações, algo impraticável de fazer manualmente.
5. Testes de usabilidade e comportamento: o que o desenvolvedor não prevê
Desenvolvedores tendem a testar fluxos "felizes" e alguns erros óbvios. A IA pode gerar scripts que simulam comportamentos de usuário que ninguém considerou.
Exemplo de prompt e saída:
Prompt: Gere um teste Cypress que simule um usuário que:
1. Abre a página de checkout
2. Adiciona um item ao carrinho
3. Volta para a página inicial
4. Navega diretamente para a URL de confirmação de pedido (sem passar pelo checkout)
5. Tenta submeter o formulário de pagamento com dados ausentes
Saída gerada pela IA:
describe('Fluxo de usuário não linear - checkout', () => {
it('deve redirecionar para login ou mostrar erro quando usuário acessa confirmação direto', () => {
cy.visit('/checkout')
cy.get('[data-test=add-to-cart]').click()
cy.visit('/') // volta para home
cy.visit('/order/confirmation/123', { failOnStatusCode: false })
// Deve redirecionar ou mostrar mensagem de sessão expirada
cy.url().should('not.include', '/order/confirmation/')
cy.contains('Sessão expirada').should('be.visible')
})
it('deve bloquear submissão de pagamento com dados ausentes após navegação direta', () => {
cy.visit('/checkout')
cy.get('[data-test=add-to-cart]').click()
cy.visit('/payment', { failOnStatusCode: false })
cy.get('[data-test=submit-payment]').click()
cy.contains('Campo obrigatório').should('be.visible')
})
})
6. Manutenção de testes gerados por IA: como não perder o controle
Testes gerados por IA podem se acumular rapidamente. Algumas estratégias para manter o controle:
- Execução em sandbox: antes de integrar ao pipeline, execute os testes gerados em um ambiente isolado e compare os resultados com um conjunto de "expected failures" conhecidos.
- Refatoração automática: use a própria IA para deduplicar testes. Prompt: "Analise esta suíte de testes e remova redundâncias, mantendo apenas os cenários únicos."
- Versionamento de prompts: mantenha um arquivo
tests/prompts.mdcom o prompt que gerou cada família de testes. Isso permite regenerar testes quando o código muda.
7. Limitações e cuidados éticos: quando não confiar na IA
A IA não substitui o julgamento humano. Três riscos principais:
- Alucinação em asserções: a IA pode gerar testes que passam falsamente porque a asserção está errada. Sempre revise a lógica das asserções.
- Dependência excessiva: confiar cegamente na IA para gerar todos os testes pode atrofiar a habilidade de pensar em cenários críticos. Use a IA como amplificadora, não substituta.
- Privacidade: nunca forneça dados reais de produção para a IA gerar testes. Use schemas anonimizados e dados sintéticos.
A IA é uma ferramenta poderosa para escrever testes que você não escreveria sozinho — não por preguiça, mas porque o custo mental de imaginar todas as variações possíveis é proibitivo. Ao delegar a geração de cenários de borda, combinações de parâmetros e fluxos não lineares, você libera sua mente para o que realmente importa: projetar sistemas robustos e revisar criticamente o que foi gerado.
Referências
- OpenAI Prompt Engineering Guide — Guia oficial da OpenAI com técnicas para criar prompts eficazes para geração de código e testes.
- Hypothesis Documentation: Property-Based Testing — Documentação completa da biblioteca Hypothesis para testes baseados em propriedades em Python.
- Cypress Documentation: Writing and Organizing Tests — Guia oficial do Cypress para testes de interface, incluindo fluxos de usuário não lineares.
- Pytest Documentation: Mock and Monkeypatch — Como usar mocks e patches no pytest para simular dependências externas em testes de integração.
- Google Testing Blog: Property-Based Testing with AI — Artigo do Google sobre como combinar IA generativa com testes de propriedade para aumentar cobertura.
- GitHub Copilot: Generating Unit Tests — Guia oficial do GitHub Copilot para geração de testes automatizados.