Kaique Mitsuo Silva Yamamoto
Arquitetura software

TDD na Pratica: Next.js e Spring Boot

Test-Driven Development (TDD) e a disciplina de escrever teste antes do codigo de producao, com ciclos curtos de feedback.


Ciclo Red-Green-Refactor

  1. Red: escreva um teste que falha.
  2. Green: implemente o minimo para passar.
  3. Refactor: melhore design sem quebrar teste.

TDD e mais sobre design incremental do que sobre "ter muitos testes".


Exemplo simples de regra (TypeScript)

import { describe, it, expect } from "vitest"
import { calculateDiscount } from "./calculate-discount"

describe("calculateDiscount", () => {
  it("aplica 15% para cliente VIP", () => {
    expect(calculateDiscount("VIP", 200)).toBe(170)
  })
})
export function calculateDiscount(customerType: string, amount: number) {
  if (customerType === "VIP") return amount * 0.85
  return amount
}

TDD em Next.js

Abordagem recomendada:

  • testar primeiro domain e application;
  • usar integration tests em route handlers;
  • evitar iniciar com E2E quando a regra ainda esta mudando.

Exemplo de use case com porta:

export interface OrderRepository {
  save(order: { id: string; amount: number }): Promise<void>
}

export class CreateOrderUseCase {
  constructor(private readonly orders: OrderRepository) {}

  async execute(input: { id: string; amount: number }) {
    if (input.amount <= 0) throw new Error("Invalid amount")
    await this.orders.save(input)
    return { id: input.id }
  }
}

Teste:

it("falha para amount invalido", async () => {
  const repo: OrderRepository = { save: async () => {} }
  const useCase = new CreateOrderUseCase(repo)
  await expect(useCase.execute({ id: "1", amount: 0 })).rejects.toThrow("Invalid amount")
})

TDD em Spring Boot

Abordagem:

  • JUnit 5 para unidade;
  • Mockito para doubles de porta;
  • SpringBootTest para integracao seletiva.
class CreateOrderUseCaseTest {
  @Test
  void shouldFailWhenAmountIsInvalid() {
    OrderRepositoryPort repo = Mockito.mock(OrderRepositoryPort.class);
    CreateOrderUseCase useCase = new CreateOrderUseCase(repo);
    assertThrows(IllegalArgumentException.class,
      () -> useCase.execute(new CreateOrderInput("1", BigDecimal.ZERO)));
  }
}

Estrutura de suite recomendada

  • unit: dominio, value objects, use cases.
  • contract: adaptadores (DB/API/fila) com contrato esperado.
  • integration: fluxo de persistencia e HTTP.
  • e2e: poucas jornadas criticas.

Distribuicao pragmatica:

  • 60% unidade
  • 30% integracao/contrato
  • 10% e2e

Armadilhas comuns

  • Testar implementacao interna em vez de comportamento.
  • Mockar tudo e perder confianca no teste.
  • Testes lentos demais no ciclo local.
  • Ignorar refatoracao depois do green.
  • Acoplar teste a framework em vez de regra.

Checklist de qualidade de testes

  • O nome do teste expressa regra de negocio?
  • Teste passa sem depender de clock/rede real?
  • Setup esta minimo e claro?
  • Falha do teste indica causa precisa?
  • Refatoracao manteve suite verde?

Recursos recomendados

YouTube

Artigos e documentacao

Papers e evidencias

Livros

Posts no X (opcional)


Conclusao

TDD cria feedback rapido e puxa design para baixo acoplamento. Em Next.js e Spring Boot, o maior ganho aparece quando as regras ficam em camadas testaveis antes de qualquer detalhe de framework.