Use Cases (Atualizado: 02/06/2026)

Guia de TDD com Claude Code: desenvolvimento orientado a testes com Vitest e node:test

Pratique TDD com Claude Code usando exemplos de Vitest, node:test, CI, hooks e templates de prompt.

Guia de TDD com Claude Code: desenvolvimento orientado a testes com Vitest e node:test

Claude Code consegue escrever código muito rápido, mas velocidade não é a mesma coisa que segurança. Os problemas costumam aparecer depois: um caso de borda esquecido, uma regressão, um desconto calculado errado ou um teste que só falha no CI.

TDD, ou desenvolvimento orientado a testes, ajuda a controlar essa velocidade. O ciclo é simples: escrever primeiro um teste que falha, fazer a menor implementação que o torna verde e depois refatorar sem mudar o comportamento. Esse ciclo é conhecido como Red-Green-Refactor.

Claude Code funciona bem nesse fluxo porque consegue listar casos, ler erros, implementar mudanças pequenas e ajustar CI. O cuidado é não pedir apenas “crie a funcionalidade”. Peça primeiro a prova da falha. Este artigo traz exemplos copiáveis com Vitest e node:test, CI, hooks atuais do Claude Code e templates de prompt.

Para esta atualização, consultei a documentação oficial de Claude Code hooks, Claude Code memory, Claude Code settings, Vitest Getting Started, Vitest CLI e Node.js test runner. O exemplo de hook lê JSON pelo stdin e usa tool_input.file_path, alinhado com a documentação atual.

Onde Claude Code ajuda no TDD

Claude Code pode escrever testes, interpretar falhas, fazer a implementação mínima, configurar CI e resumir riscos. A pessoa responsável ainda define regras de negócio, segurança, contrato de API e decisão de publicação.

EtapaTarefa para Claude CodeRevisão humana
RedEscrever testes que falhamEle inventou requisitos?
GreenImplementar o mínimoHá abstração desnecessária?
RefactorMelhorar nomes e duplicaçãoO comportamento ficou igual?
CIRodar testes em cada PRA versão do Node é realista?
OperaçãoUsar hooks e CLAUDE.mdA automação está rápida?
flowchart LR
  A["Dividir a especificação"] --> B["Red: teste falhando"]
  B --> C["Green: implementação mínima"]
  C --> D["Refactor: limpeza"]
  D --> E["CI e hooks rodam de novo"]
  E --> B

Exemplo 1: preços e cupons com Vitest

Lógica de preço, cupons e assinaturas é um bom ponto de partida porque erros pequenos afetam receita.

npm install -D vitest
{
  "type": "module",
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest"
  },
  "devDependencies": {
    "vitest": "^3.0.0"
  }
}

Crie primeiro src/cart.test.ts.

import { describe, expect, it } from "vitest";
import { priceCart, ValidationError } from "./cart";

describe("priceCart", () => {
  it("calculates subtotal and total without a coupon", () => {
    const result = priceCart({
      items: [
        { sku: "book", unitPriceCents: 1200, quantity: 2 },
        { sku: "video", unitPriceCents: 3000, quantity: 1 },
      ],
    });

    expect(result).toEqual({
      subtotalCents: 5400,
      discountCents: 0,
      totalCents: 5400,
    });
  });

  it("applies a valid percent coupon", () => {
    const result = priceCart(
      {
        items: [{ sku: "course", unitPriceCents: 10000, quantity: 1 }],
        coupon: {
          code: "SPRING20",
          percentOff: 20,
          expiresAt: "2026-06-30T00:00:00.000Z",
        },
      },
      { now: new Date("2026-06-02T00:00:00.000Z") },
    );

    expect(result.totalCents).toBe(8000);
    expect(result.discountCents).toBe(2000);
  });

  it("rejects expired coupons", () => {
    expect(() =>
      priceCart(
        {
          items: [{ sku: "course", unitPriceCents: 10000, quantity: 1 }],
          coupon: {
            code: "OLD20",
            percentOff: 20,
            expiresAt: "2026-05-01T00:00:00.000Z",
          },
        },
        { now: new Date("2026-06-02T00:00:00.000Z") },
      ),
    ).toThrow(ValidationError);
  });

  it("rejects zero or negative quantity", () => {
    expect(() =>
      priceCart({
        items: [{ sku: "book", unitPriceCents: 1200, quantity: 0 }],
      }),
    ).toThrow("quantity must be positive");
  });
});

Peça a Claude Code para confirmar o Red.

Estamos na etapa Red. src/cart.test.ts existe, mas src/cart.ts ainda não existe.

Faça:
1. Execute npm test e confirme a falha.
2. Implemente apenas o src/cart.ts mínimo para passar.
3. Não adicione UI, banco, API externa ou recursos futuros.
4. Refatore apenas depois do Green.

Implementação mínima de src/cart.ts.

export class ValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "ValidationError";
  }
}

type CartItem = {
  sku: string;
  unitPriceCents: number;
  quantity: number;
};

type Coupon = {
  code: string;
  percentOff: number;
  expiresAt: string;
};

type CartInput = {
  items: CartItem[];
  coupon?: Coupon;
};

type PriceOptions = {
  now?: Date;
};

export function priceCart(input: CartInput, options: PriceOptions = {}) {
  if (input.items.length === 0) {
    throw new ValidationError("cart must contain at least one item");
  }

  const subtotalCents = input.items.reduce((sum, item) => {
    if (!Number.isInteger(item.quantity) || item.quantity <= 0) {
      throw new ValidationError("quantity must be positive");
    }
    if (!Number.isInteger(item.unitPriceCents) || item.unitPriceCents < 0) {
      throw new ValidationError("unitPriceCents must be a non-negative integer");
    }
    return sum + item.unitPriceCents * item.quantity;
  }, 0);

  const discountCents = calculateDiscount(subtotalCents, input.coupon, options.now ?? new Date());

  return {
    subtotalCents,
    discountCents,
    totalCents: subtotalCents - discountCents,
  };
}

function calculateDiscount(subtotalCents: number, coupon: Coupon | undefined, now: Date) {
  if (!coupon) return 0;

  if (coupon.percentOff <= 0 || coupon.percentOff > 100) {
    throw new ValidationError("percentOff must be between 1 and 100");
  }

  if (new Date(coupon.expiresAt).getTime() < now.getTime()) {
    throw new ValidationError("coupon expired");
  }

  return Math.round(subtotalCents * (coupon.percentOff / 100));
}

Exemplo 2: limites de CLI com node:test

Para utilitários pequenos em Node, node:test reduz dependências. Salve como limit.test.mjs.

import test from "node:test";
import assert from "node:assert/strict";

export function parseLimit(value, fallback = 20) {
  if (value === undefined || value === "") return fallback;

  const parsed = Number(value);
  if (!Number.isInteger(parsed)) {
    throw new TypeError("limit must be an integer");
  }
  if (parsed < 1 || parsed > 100) {
    throw new RangeError("limit must be between 1 and 100");
  }

  return parsed;
}

test("parseLimit uses fallback when the value is empty", () => {
  assert.equal(parseLimit(undefined), 20);
  assert.equal(parseLimit("", 50), 50);
});

test("parseLimit accepts values from 1 to 100", () => {
  assert.equal(parseLimit("1"), 1);
  assert.equal(parseLimit("100"), 100);
});

test("parseLimit rejects decimals and out-of-range values", () => {
  assert.throws(() => parseLimit("1.5"), /integer/);
  assert.throws(() => parseLimit("0"), /between 1 and 100/);
  assert.throws(() => parseLimit("101"), /between 1 and 100/);
});
node --test limit.test.mjs

Exemplo 3: bug de API como regressão

Quando um bug chega à produção, transforme-o primeiro em teste falhando.

Adicione um teste de regressão com TDD.

Contexto:
- POST /checkout aceita cupons expirados por engano.
- Cupons válidos e checkout sem cupom devem continuar funcionando.

Red:
- Adicione teste que espera 400 para cupom expirado.
- Confirme que a implementação atual falha.

Green:
- Faça a menor mudança possível na API.

Refactor:
- Extraia apenas a comparação de datas duplicada.

Retorne:
- Nome do teste, saída da falha, arquivos alterados, comandos e riscos.

Veja também o guia de testes de API e o guia de estratégia de testes.

CI e hooks

name: test
on:
  pull_request:
  push:
    branches: [main]

jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm test

Hook para rodar Vitest relacionado depois de edições.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node .claude/hooks/run-related-vitest.mjs",
            "timeout": 120
          }
        ]
      }
    ]
  }
}
import { spawnSync } from "node:child_process";
import path from "node:path";

let raw = "";
for await (const chunk of process.stdin) {
  raw += chunk;
}

const event = raw ? JSON.parse(raw) : {};
const filePath = event.tool_input?.file_path;

if (typeof filePath !== "string" || !/\.[cm]?[jt]sx?$/.test(filePath)) {
  process.exit(0);
}

const target = path.isAbsolute(filePath)
  ? path.relative(process.cwd(), filePath)
  : filePath;

const result = spawnSync("npx", ["vitest", "related", target, "--run"], {
  stdio: "inherit",
  shell: process.platform === "win32",
});

process.exit(result.status ?? 1);

Em CLAUDE.md, deixe uma regra curta.

## TDD workflow
- Behavior changes start with a failing test.
- Show the Red result before implementation.
- Implement the smallest change that makes the test pass.
- Refactor only after the targeted test is Green.
- Report the command, result, changed files, and remaining risk.

Para mais detalhes, leia o guia de hooks e as boas práticas de CLAUDE.md.

Templates de prompt

Nova funcionalidade com TDD:
Objetivo:
  Adicionar [funcionalidade].
Especificação:
  - [caminho feliz]
  - [valores de borda]
  - [comportamento de erro]
Processo:
  1. Escrever testes primeiro.
  2. Rodar npm test e mostrar Red.
  3. Implementar o mínimo para Green.
  4. Refatorar sem mudar comportamento.
Retorno:
  Saída da falha, comandos, arquivos alterados e riscos.
Correção de bug com TDD:
Reprodução:
  [entrada, ação ou log]
Esperado:
  [comportamento correto]
Atual:
  [comportamento atual]
Pedido:
  Primeiro adicione um teste de regressão que falha.
  Depois faça a menor correção.
  Não enfraqueça nem apague testes existentes sem explicar.
Refatoração segura:
Alvo:
  [arquivo/função]
Restrição:
  O comportamento público não muda.
Passos:
  1. Adicionar characterization tests.
  2. Confirmar Green.
  3. Alterar apenas estrutura interna.
  4. Rodar os mesmos testes.

Armadilhas comuns

A primeira é pular o Red. Se o teste já passa antes da correção, ele não protege nada. A segunda é testar detalhes internos em vez de comportamento. A terceira é usar tempo real em teste; injete now. A quarta é confiar só em mocks para pagamento, email ou CRM. A quinta é deixar Claude Code apagar testes para chegar ao Green.

CTA

Comece com uma regra de preço, um parser de CLI ou uma regressão de API. Quem trabalha sozinho pode usar a folha gratuita de Claude Code e os templates acima. Para prompts, hooks e checklists reutilizáveis, veja os produtos ClaudeCodeLab. Equipes que precisam de TDD, CI, permissões e revisão em um repositório real podem usar treinamento e consultoria Claude Code.

Resultado ao testar

No fluxo de Masa, pedir primeiro o teste falhando reduziu o tempo de revisão. Cupom expirado, quantidade zero e rotas API sem autenticação apareceram mais cedo. Rodar todos os testes por hook ficou lento; a configuração prática passou a rodar Vitest relacionado após edições e E2E completo no CI.

#Claude Code #TDD #test-driven development #testing #quality assurance
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.