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:

  1. 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.

  2. 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.

  3. 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.md com 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:

  1. 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.
  2. 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.
  3. 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