“`html
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.
Integração: colaboração real
Balanceamento: custo x confiança
Estratégia: menos flakiness
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).
ele provavelmente está no lugar errado (ou precisa de um nível diferente de isolamento/infra).
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?”.
Se isso acontece, o teste está misturando camadas e perde valor.
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.
para validar comportamento entre componentes. Se é regra local, eu vou de unitário.
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.
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);
});
});
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.
“`
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!