TDD: invertendo o desenvolvimento

O teste é o contrato auditável da spec. A IA escreve
o código. O humano rege o ciclo.

Canal Sandeco

O fluxo de sempre.

Abre o editor, escreve o código, executa. Se sobrar tempo, escreve algum teste.

A prática tradicional

Código primeiro. Teste depois, no fim do dia.

A ordem clássica

Escreve a função

Executa o código

Teste, se sobrar tempo

A ordem se inverte.

No TDD, pensa o comportamento, escreve o teste que falha, depois o código mínimo.

A inversão TDD

O teste vem antes do código, não depois.

A troca que reorganiza tudo

Comportamento primeiro

Teste que falha

Código que passa

Arquitetura sustentada

Red, Green, Refactor.

O teste vem antes do código. O ciclo da IA mantém o ritmo.

Três fases, um ciclo

A IA executa, o humano governa.

A nova divisão de trabalho

Humano define intenção

A spec é a fonte do comportamento

Agente executa o ciclo

Escreve teste, codifica, refatora

Teste arbitra

Passou está aceito, falhou volta

Exemplo de quinze segundos: a função soma.

Primeiro o teste falha (Red). Depois o código mínimo passa (Green).

test_calculadora.py RED
# o teste vem antes do código

from calculadora import soma

def test_soma():
    assert soma(2, 3) == 5
calculadora.py GREEN
# implementação mínima para passar

def soma(a, b):
    return a + b

O ciclo se fecha em três passos

1. pytest falha

calculadora.py ainda não existe

2. pytest passa

implementação mínima vira a luz verde

3. refatora

limpar com a rede de segurança ativa

A chave vem antes da fechadura.

A spec é a promessa. O teste é o auditor que destranca o código.

Spec, teste e código se encaixam

A chave girou? O código está correto.

A regra que protege o contrato

Spec descreve

Linguagem que humanos leem

Teste verifica

Asserção que a máquina executa

Não altere o teste

Mexer no contrato é falsificar a auditoria

Passo 1: a spec em texto.

Uma frase de regra vira o contrato que vai guiar o teste e o código.

RULES.md
# Regra de desconto

Compras com valor maior ou igual a 100 reais
recebem 10% de desconto sobre o valor total.

Compras abaixo desse limite não recebem desconto.

A spec define a intenção antes de qualquer linha de código

Passo 2: o teste que falha.

A spec vira asserts. O teste falha porque o código ainda não existe.

test_desconto.py RED
from compras import calcular_valor_final

def test_acima_de_100():
    assert calcular_valor_final(100) == 90
    assert calcular_valor_final(200) == 180

def test_abaixo_de_100():
    assert calcular_valor_final(99) == 99
    assert calcular_valor_final(50) == 50

O teste é o contrato verificável da spec

Passo 3: o código que passa.

O agente implementa o mínimo necessário para virar os testes em verde.

compras.py GREEN
# o agente implementa respeitando o teste

def calcular_valor_final(valor):
    if valor >= 100:
        return valor * 0.9

    return valor
PROMPT-PADRÃO TDD Use TDD para implementar esta funcionalidade. Siga o protocolo, sem pular etapas. 1. Leia a spec inteira antes de qualquer código. 2. Crie os testes que cobrem a intenção descrita na spec. 3. Execute os testes e confirme que todos falham antes de implementar. 4. Implemente apenas o mínimo necessário para virar os testes em verde. 5. Rode os testes novamente e confirme que todos passam. 6. Refatore o código mantendo todos os testes verdes. Não altere nenhum teste sem aprovação explícita do humano.

O contrato fechou: spec, teste e código apertam as mãos

Cinco camadas. Uma pirâmide.

Muitos testes baratos na base, poucos testes caros no topo.

Cada camada responde uma pergunta

Custo cresce, quantidade encolhe.

Stack mínima em Python

pytest

framework principal

pytest-mock

substitui dependências

coverage.py

mede cobertura

hypothesis

property-based

Unitário: a função sob o microscópio.

Uma função pura, dezenas de entradas, uma certeza por linha.

A menor unidade do código

Função isolada, sem rede, sem disco, sem surpresa.

A unidade mínima que o teste protege

Função pura

sem efeito colateral

Mocks de externos

DB, rede, tempo

Milissegundos

milhares por segundo

pytest, unittest

o ferramental clássico

Como você escreve um teste unitário.

Arrange, Act, Assert. Uma função, uma asserção por caso.

test_calcular_desconto.py
import pytest
from precificacao import calcular_desconto

# Arrange + Act + Assert num único bloco parametrizado
@pytest.mark.parametrize("valor, esperado", [
    (100, 90),       # exatamente no limite
    (200, 180),      # acima do limite
    (99.99, 99.99),  # um centavo abaixo
    (0, 0),           # caso degenerado
])
def test_calcular_desconto(valor, esperado):
    assert calcular_desconto(valor) == esperado

Artefatos e código envolvidos

A função sob teste

precificacao.calcular_desconto, sem I/O

O arquivo de teste

tests/unit/test_calcular_desconto.py

O comando

pytest tests/unit -q

Uma skill faz o teste unitário pra você.

Você não copia prompt. Você invoca uma skill que já encapsula o protocolo inteiro.

SKILL DE TESTES UNITÁRIOS
$ claude /testes-unitarios src/precificacao.py
leu a assinatura e o docstring da função
identificou 7 classes de equivalência (válidas, inválidas, limites, degenerados)
gerou tests/unit/test_precificacao.py com 12 casos parametrizados
rodou pytest, suíte verde em 0.18s
aguarda revisão humana antes do merge

O que a skill encapsula

Prompt + protocolo

vivem dentro da skill, não no chat

Artefato gerado

tests/unit/test_*.py

Humano decide

revisar e aprovar o merge

Integração: módulos conversando.

Dois módulos. Um contrato. Uma conversa que precisa terminar.

Onde dois módulos se encontram

O teste fica no fio, não em cada ponta.

O teste que liga as peças

Múltiplos módulos

api, repo, fila

DB real ou fake

SQLite, testcontainers

Fixtures de setup

dado conhecido, estado limpo

Segundos

dezenas por suíte

Como você escreve um teste de integração.

Sobe um ambiente mínimo, executa o caso, verifica o estado final.

test_pedido_integration.py
import pytest
from app.repo import PedidoRepo
from app.servicos import criar_pedido

@pytest.fixture
def db(tmp_path):
    # SQLite isolado, descartado ao fim do teste
    return PedidoRepo(tmp_path / "test.db")

def test_pedido_persiste_no_repo(db):
    # Arrange: estado limpo, dado conhecido
    pedido = criar_pedido(db, cliente="ana", valor=120)
    # Act: a operação atravessa serviço e repo
    encontrado = db.buscar(pedido.id)
    # Assert: estado final é exatamente o esperado
    assert encontrado.cliente == "ana"
    assert encontrado.valor == 120

Artefatos e código envolvidos

Fixtures de ambiente

DB temporário, fila em memória

Pastas

tests/integration/

testcontainers

Postgres, Redis, RabbitMQ reais

Uma skill mapeia a integração e escreve o teste.

Você aponta o módulo. A skill descobre dependências e propõe as fixtures mínimas.

SKILL DE TESTES DE INTEGRAÇÃO
$ claude /testes-integracao app/servicos/pedido.py
mapeou dependências externas (PedidoRepo, FilaNotificacao)
propôs fixtures: SQLite em tmp_path, fila em memória
gerou tests/integration/test_pedido_integration.py com 5 cenários
rodou pytest -m integration, suíte verde em 2.4s
humano confirma o que é fixture e o que é dependência real

O que a skill encapsula

Mapa de dependências

descoberta automática do que sobe junto

Artefato gerado

tests/integration/test_*.py

Humano decide

o que é real, o que é fake

Contrato: o formato que não pode quebrar.

Schema de entrada, schema de saída. O resto é detalhe.

A casca da interface

O que entra, o que sai, em que forma.

A casca da interface

OpenAPI

a casca da API HTTP

JSON Schema

valida payloads e respostas

Pact

consumidor versus provedor

Snapshot

forma estável da saída

Como você escreve um teste de contrato.

Define o schema, gera o payload, valida nas duas pontas.

test_pedido_contrato.py
from pydantic import BaseModel, ValidationError
from app.api import criar_pedido_endpoint

class PedidoOut(BaseModel):
    id: int
    cliente: str
    valor: float
    status: str

def test_resposta_segue_schema():
    # O endpoint pode mudar internamente,
    # mas a forma da resposta é um contrato.
    resp = criar_pedido_endpoint({"cliente": "ana", "valor": 120})
    # Se a casca quebrar, este teste explode.
    PedidoOut.model_validate(resp)

Artefatos e código envolvidos

O schema versionado

contracts/PedidoOut.json

Pastas

tests/contract/

Validadores

pydantic, jsonschema, pact

Uma skill lê o schema e mantém o contrato.

Schema novo, teste novo. Schema mudou, alerta vermelho.

SKILL DE TESTES DE CONTRATO
$ claude /testes-contrato contracts/PedidoOut.json
leu o JSON Schema versionado
gerou exemplos válidos para cada caminho do schema
produziu contraexemplos por campo (tipo errado, range fora, formato inválido)
gravou tests/contract/test_pedido_out.py com 18 casos
registrou teste-sentinela para mudança de versão
humano aprova qualquer evolução do contrato

O que a skill encapsula

Parser + gerador

schema vira casos válidos e contraexemplos

Artefato gerado

tests/contract/test_*.py

Humano decide

evolução e versão do contrato

End-to-end: o usuário do começo ao fim.

Login, carrinho, pagamento, sucesso. Caminho real, dados reais.

O sistema todo, sem atalho

O caminho que o cliente percorre no produto.

A jornada inteira sob teste

Browser real

Chromium, Firefox, Webkit

Dados de teste

usuário fixture, cartão fake

Golden paths

os caminhos que pagam o aluguel

Minutos

poucos, mas caros

Como você escreve um teste end-to-end.

Um roteiro de cliques que termina em um estado observável.

checkout_feliz.spec.ts
import { test, expect } from "@playwright/test";

test("checkout feliz termina em pedido confirmado", async ({ page }) => {
  await page.goto("/login");
  await page.fill("#email", "ana@exemplo.com");
  await page.fill("#senha", "senha-teste");
  await page.click("text=Entrar");

  await page.click("text=Adicionar ao carrinho");
  await page.click("text=Finalizar compra");
  await page.click("text=Pagar");

  await expect(page.locator("text=Pedido confirmado")).toBeVisible();
});

Artefatos e código envolvidos

Cenários reais

login, compra, cancelamento

Pastas

tests/e2e/

Stack

Playwright, Cypress, Selenium

Uma skill converte user story em jornada navegável.

Você descreve o caminho do cliente. A skill devolve o teste navegando o app.

SKILL DE TESTES END-TO-END
$ claude /testes-e2e stories/checkout-feliz.md
leu a user story e os critérios de aceitação
identificou o caminho feliz e 2 caminhos críticos de erro
gerou tests/e2e/checkout_feliz.spec.ts com seletores semânticos (role, label)
configurou fixtures de usuário descartável e cartão fake
rodou Playwright, suíte verde em 1m 12s
humano define quais golden paths bloqueiam o deploy

O que a skill encapsula

Playwright + seletores

resistentes a refactor de UI

Artefato gerado

tests/e2e/*.spec.ts

Humano decide

quais paths bloqueiam o deploy

Regressão: o bug que não pode voltar.

Cada bug encontrado vira um teste. O cinturão cresce a cada commit.

A memória do que já doeu

Bug que entrou no teste não sai mais.

A memória do que já quebrou

Suíte fixa

cresce, nunca encolhe

Roda no CI

a cada PR, sem exceção

Snapshot

o comportamento de hoje

Histórico

um teste por bug capturado

Como você monta o cinturão de regressão.

Bug encontrado vira teste com o número da issue. Nunca sai mais.

test_regressao_ISSUE_482.py
import pytest
from precificacao import calcular_total

# ISSUE #482: total negativo aparecia quando
# cupom de desconto era maior que a soma.
def test_total_nao_pode_ser_negativo_issue_482():
    itens = [{"valor": 10}, {"valor": 15}]
    cupom = 50  # maior que a soma dos itens
    assert calcular_total(itens, cupom=cupom) == 0

Artefatos e código envolvidos

Teste por bug

nome carrega o número da issue

Pastas

tests/regression/

CI bloqueia

PR não sobe com regressão vermelha

Uma skill prende o bug no cinturão.

A skill recebe o número da issue, reproduz o bug e adiciona ao cinturão de regressão.

SKILL DE TESTES DE REGRESSÃO
$ claude /teste-regressao --issue 482
leu o relato, o stack trace e o commit de origem (a3b1c2d)
construiu o caso mínimo que reproduz o bug
confirmou que o teste falha no código antigo
gravou tests/regression/test_regressao_ISSUE_482.py
travou no cinturão: PR vermelho até o teste passar
humano decide quando o bug está realmente curado

O que a skill encapsula

Reprodutor + cinturão

caso mínimo + integração com CI

Artefato gerado

tests/regression/test_ISSUE_*.py

Humano decide

quando o bug está realmente curado

A pirâmide vira estrutura de pastas.

Olhar para tests/ já revela como o projeto pensa sobre validação.

tests/
├── unit/ # funções isoladas
├── integration/ # módulos juntos
├── contract/ # forma das APIs
├── e2e/ # fluxo do usuário
└── regression/ # bugs já vistos
specs/
├── PRD.md
├── RULES.md
├── API_SPEC.md
└── TESTS_SPEC.md
terminal
# roda toda a pirâmide em ordem de custo
$ pytest tests/unit/
$ pytest tests/integration/
$ pytest tests/contract/
$ pytest tests/e2e/
$ pytest tests/regression/

# mede cobertura
$ pytest --cov=src tests/unit/

# property-based testing
$ pytest tests/unit/test_invariantes.py

# todo bug corrigido vira teste
# na pasta de regressão, sem exceção

A regra de ouro dos testes

CI roda em ordem

Falha cedo, economiza minutos

Bug vira teste

A regressão guarda a memória

Lê em 30 segundos

Programador novo entende sozinho

O humano no comando.

A IA digita o código. Cinco responsabilidades continuam humanas.

A bússola do projeto está nas mãos do humano

Cinco pontos cardeais que o agente não absorve.

A síntese do capítulo

SDD escreve a partitura

A spec descreve a intenção

TDD afina a orquestra

O teste arbitra o resultado

Humano rege

A IA toca os instrumentos