Testes Unitários vs Integração: Onde Focar e Como Escolher

Testes Unitários vs Integração: Onde Focar e Como Escolher

“`html





Testes Unitários vs Integração: onde focar?


YU

Testes unitários vs integração: onde focar?

Se você quer uma suíte de testes que ajude de verdade (e não vire um peso), a diferença entre unidade e integração
precisa virar decisão explícita: o que testar em cada camada e como manter o feedback rápido.

Unitários: regra/contrato
Integração: colaboração real
Balanceamento: custo x confiança
Estratégia: menos flakiness

Entenda a intenção

Teste unitário não é “para aumentar cobertura”: é para estabilizar decisões

Para mim, a melhor forma de diferenciar é pela intenção do teste:
unitário valida lógica local e contratos (entradas/saídas, invariantes,
comportamento de domínio) com dependências controladas; integração valida interações
entre componentes (ex.: aplicação + banco, aplicação + filas/HTTP, migrações + schemas).

Regra prática: se o teste quebra por “algo fora do seu controle” (rede, timing, ambiente),
ele provavelmente está no lugar errado (ou precisa de um nível diferente de isolamento/infra).

Heurísticas de escolha

Onde focar em unitários: domínio, regras e bordas

Eu priorizo testes unitários quando o sistema tem decisões claras e efeitos determinísticos.
Exemplos típicos:

  • Validação de regras: preços, permissões, cálculo de impostos, validação de estados e transições.
  • Invariantes do domínio: “nunca pode acontecer X”, “se Y então Z”, “IDs seguem formato”.
  • Comportamento de adaptadores com dependências fake/mocks: mapeamentos, conversões e formatação.
  • Casos de borda: null/empty, limites numéricos, timezone, ordenação, idempotência lógica.

Um bom teste unitário responde rapidamente perguntas como:
“o método decide corretamente?”, “o erro correto é levantado?”, “o contrato de retorno é respeitado?”.

Anti-pattern comum: unitário que faz requisição real, acessa banco real ou depende de ambiente.
Se isso acontece, o teste está misturando camadas e perde valor.

Aplicando integração com propósito

Onde focar em integração: confiança na colaboração e no caminho crítico

Integração entra onde existe risco de divergência entre componentes.
Em geral, eu uso integração para reduzir “surpresas” em tempo de execução:

  • Persistência: mapeamento ORM, constraints, migrações e consultas reais.
  • Contratos de infraestrutura: filas, eventos, webhooks, chamadas HTTP com payloads reais.
  • Fluxos fim-a-fim no domínio do produto: criação de pedido, cobrança, emissão, cancelamento.
  • Serialização/compatibilidade: schemas, versionamento de eventos, parsing de datas/campos.

A mentalidade que funciona bem é: integração deve aumentar confiança sem explodir o tempo de execução.
Então eu priorizo caminhos críticos e pontos de falha caros, não todas as combinações possíveis.

Dica de foco: se um fluxo tem alto impacto (dinheiro, dados sensíveis, reputação), eu escolho integração
para validar comportamento entre componentes. Se é regra local, eu vou de unitário.

Equilíbrio e manutenção

Como equilibrar para não virar manutenção infinita

O problema geralmente não é “falta de testes”: é falta de estratégia.
Eu tento manter três objetivos:

  • Feedback rápido: unitários devem rodar em poucos segundos e dar sinal claro.
    Integração pode ser mais lenta, mas precisa ser previsível.
  • Diagnóstico fácil: quando falha, o teste deveria apontar “onde” e “por quê”.
    Unitários tendem a ser mais específicos; integrações devem ser bem desenhadas para reduzir ruído.
  • Baixa flakiness: integração não pode depender de timing frágil.
    Se precisa esperar, eu imponho limites e estabilizo o ambiente.

Um jeito prático de orientar decisões é perguntar:
o que esse teste precisa provar?
Se for “a regra”, vai para unitário. Se for “a colaboração real”, vai para integração.

Critério que eu uso: quanto maior o custo de mudar o sistema (ex.: mexer em banco/infra),
maior a importância de ter integração naquele ponto específico.

// Exemplo (pseudo-TS/Node) mostrando separação por intenção.
// Unitário: regra + contrato, sem tocar infra.
// Integração: valida colaboração com banco e repositório reais.

//////////////////////////////
// Unitário (regra do domínio)
//////////////////////////////

class Money {
  constructor(readonly cents: number) {}
}

function calculateTotal(items: Array<{ cents: number; qty: number }>, discountPct: number) {
  const subtotal = items.reduce((acc, it) => acc + it.cents * it.qty, 0);
  if (discountPct < 0 || discountPct > 100) throw new Error("discountPct inválido");
  const discount = Math.round(subtotal * (discountPct / 100));
  return new Money(subtotal - discount);
}

// Teste unitário
describe("calculateTotal", () => {
  it("aplica desconto e mantém determinismo", () => {
    const total = calculateTotal([{ cents: 1000, qty: 3 }], 10);
    expect(total.cents).toBe(2700); // 3000 - 300
  });

  it("rejeita desconto fora do intervalo", () => {
    expect(() => calculateTotal([{ cents: 1000, qty: 1 }], 101)).toThrow("discountPct inválido");
  });
});

//////////////////////////////
// Integração (colaboração)
//////////////////////////////

// A partir daqui, suponha um repositório real que escreve/consulta o banco.
async function createOrder(repo, payload) {
  // regra de negócio mínima + persistência
  // (a regra principal já deveria estar testada em unitários)
  const order = await repo.create(payload);
  return order;
}

describe("createOrder (integração)", () => {
  it("persiste e recupera corretamente a ordem no banco", async () => {
    const repo = new OrderRepository(realDbClient()); // banco de teste
    const created = await createOrder(repo, { customerId: "c1", totalCents: 2700 });

    const found = await repo.findById(created.id);
    expect(found).not.toBeNull();
    expect(found.totalCents).toBe(2700);
  });
});

Checklist rápido

Decisão em 30 segundos: unidade ou integração?

  • Você quer testar uma regra determinística? → Unitário.
  • Você quer garantir que banco/eventos/HTTP colaboram certo? → Integração.
  • O teste falha por ambiente/timeouts/ordem de execução? → reveja isolamento (ou mova o teste).
  • O fluxo é crítico para o produto (dados, dinheiro, consistência)? → Integração no caminho.
  • Você está duplicando a mesma lógica em várias integrações? → extraia para unitários e reduza acoplamento.
O objetivo final é uma suíte que explica falhas: unitários para decisões e integração para contrato entre componentes.



“`