Arquitetura softwareFrontend webTypeScriptTestes
TDD — Test-Driven Development
Baixar PDFO ciclo Red-Green-Refactor, quando usar TDD e quando não — exemplos práticos full-stack.
TDD — Test-Driven Development
TDD não é sobre testes. TDD é um método de design que usa testes como ferramenta. O teste vem antes do código — ele define o que o código deve fazer.
O ciclo: Red → Green → Refactor
1. RED → Escreva um teste que FALHA (o código não existe ainda)
2. GREEN → Escreva o código MÍNIMO para o teste passar
3. REFACTOR → Melhore o código sem quebrar o teste
4. RepitaAnalogia
É como construir uma casa:
- Red: "Preciso de uma porta que abre e fecha" (requisito/teste)
- Green: Prega duas tábuas com uma dobradiça (solução mínima)
- Refactor: Troca por uma porta de verdade, com fechadura (melhoria)
- Se em algum momento a porta para de abrir → você sabe exatamente o que quebrou
Exemplo prático — Sistema de desconto (Backend)
Passo 1: RED — Escreve o teste primeiro
// services/__tests__/discount.service.test.ts
import { describe, it, expect } from 'vitest';
describe('DiscountService', () => {
it('aplica 10% de desconto para compras acima de R$100', () => {
const service = new DiscountService(); // ❌ não existe ainda
const result = service.calculate(150);
expect(result).toBe(135); // 150 * 0.9
});
it('não aplica desconto para compras abaixo de R$100', () => {
const service = new DiscountService();
const result = service.calculate(80);
expect(result).toBe(80);
});
it('aplica 20% para compras acima de R$500', () => {
const service = new DiscountService();
const result = service.calculate(600);
expect(result).toBe(480); // 600 * 0.8
});
});❌ RED — DiscountService não existePasso 2: GREEN — Código mínimo para passar
// services/discount.service.ts
export class DiscountService {
calculate(amount: number): number {
if (amount > 500) return amount * 0.8;
if (amount > 100) return amount * 0.9;
return amount;
}
}✅ GREEN — todos os testes passamPasso 3: REFACTOR — Melhora sem quebrar
// services/discount.service.ts
interface DiscountTier {
minAmount: number;
discount: number; // 0.1 = 10%
}
const DISCOUNT_TIERS: DiscountTier[] = [
{ minAmount: 500, discount: 0.2 },
{ minAmount: 100, discount: 0.1 },
];
export class DiscountService {
calculate(amount: number): number {
const tier = DISCOUNT_TIERS.find(t => amount >= t.minAmount);
return tier ? amount * (1 - tier.discount) : amount;
}
}✅ GREEN — testes ainda passam, código mais limpoPasso 4: Novo teste → RED → GREEN → REFACTOR
it('aplica desconto cumulativo para cliente VIP', () => {
const service = new DiscountService();
const result = service.calculate(200, { isVip: true });
expect(result).toBe(160); // 200 * 0.9 (base) * ~1.11... ou 20% flat para VIP
});❌ RED — não aceita segundo parâmetro
→ GREEN → REFACTOR → repeteExemplo prático — Componente React (Frontend)
RED — Teste primeiro
// components/__tests__/PriceTag.test.tsx
describe('PriceTag', () => {
it('exibe preço formatado em BRL', () => {
render(<PriceTag amount={29.9} />);
expect(screen.getByText('R$\u00a029,90')).toBeInTheDocument();
});
it('mostra preço original riscado quando há desconto', () => {
render(<PriceTag amount={29.9} originalPrice={49.9} />);
expect(screen.getByText('R$\u00a049,90')).toBeInTheDocument();
expect(screen.getByText('R$\u00a029,90')).toBeInTheDocument();
});
it('não mostra preço original quando não há desconto', () => {
render(<PriceTag amount={29.9} />);
expect(screen.queryByText(/riscado/i)).not.toBeInTheDocument();
});
});GREEN — Componente mínimo
// components/PriceTag.tsx
interface PriceTagProps {
amount: number;
originalPrice?: number;
}
export function PriceTag({ amount, originalPrice }: PriceTagProps) {
return (
<div>
{originalPrice && <span className="line-through">{formatBRL(originalPrice)}</span>}
<span>{formatBRL(amount)}</span>
</div>
);
}REFACTOR — Melhora apresentação
export function PriceTag({ amount, originalPrice }: PriceTagProps) {
const hasDiscount = originalPrice && originalPrice > amount;
const discountPercent = hasDiscount
? Math.round((1 - amount / originalPrice) * 100)
: 0;
return (
<div className="flex items-center gap-2">
{hasDiscount && (
<>
<span className="text-gray-400 line-through">{formatBRL(originalPrice)}</span>
<span className="text-green-600 text-sm">-{discountPercent}%</span>
</>
)}
<span className="text-xl font-bold">{formatBRL(amount)}</span>
</div>
);
}Quando usar TDD
| Use TDD quando | Não use TDD quando |
|---|---|
| Regra de negócio clara | Explorando/experimentando |
| Lógica complexa com muitos casos | UI puramente visual |
| Funções puras | Prototipagem rápida |
| Código que será reutilizado | Script descartável |
| Bug fix (reproduz o bug como teste) | Configuração de infraestrutura |
Regras de ouro do TDD
- Um teste por comportamento — não teste 5 coisas em um teste
- Nomes descritivos —
it('rejeita email sem @')nãoit('testa email') - AAA: Arrange, Act, Assert — prepare, execute, verifique
- Nunca pule o refactor — código mínimo verde ≠ código bom
- Testes devem ser independentes — a ordem dos testes não importa