Capa do Ebook gratuito Qualidade de Software com Métricas: Do Bug ao Indicador de Processo

Qualidade de Software com Métricas: Do Bug ao Indicador de Processo

Novo curso

20 páginas

Cobertura útil e risco residual de mudanças

Capítulo 8

Tempo estimado de leitura: 0 minutos

+ Exercício

Cobertura útil e risco residual de mudanças

Quando se fala em “cobertura”, muita gente pensa apenas em um número: 80%, 90%, 100%. Só que, para tomar decisões de engenharia e de processo, o que interessa é a cobertura útil: a parte da cobertura de testes que realmente reduz risco de regressão e aumenta a confiança para mudar o sistema. Em paralelo, mesmo com bons testes, sempre sobra um risco residual — a probabilidade e o impacto de algo dar errado após uma mudança, apesar das barreiras existentes. Este capítulo conecta os dois conceitos de forma prática: como avaliar se sua cobertura é “útil” e como estimar/gerenciar o risco que permanece depois de uma alteração.

O que é “cobertura útil” (e o que não é)

Cobertura útil é a parcela da cobertura de testes que está alinhada ao comportamento que importa (requisitos, regras de negócio, contratos, integrações, invariantes) e que falha quando uma mudança relevante quebra algo. Ela é “útil” porque:

  • Detecta regressões reais (mudanças que alteram comportamento esperado).
  • É estável (não falha por motivos frágeis como timing, dependência de dados voláteis, ordem de execução).
  • É representativa (cobre cenários críticos e bordas, não apenas caminhos felizes).
  • É rápida o suficiente para rodar com frequência e apoiar o fluxo de mudança.

Já a cobertura “não útil” costuma aparecer quando:

  • Testes exercitam linhas de código, mas não verificam nada relevante (asserts fracos, snapshots sem intenção, “testes que só rodam”).
  • muito teste de implementação (acoplado a detalhes internos), que quebra com refatorações e gera ruído.
  • Os testes são flaky (intermitentes), reduzindo confiança e levando a reexecuções ou ignorar falhas.
  • A suíte é tão lenta que vira “teste noturno”, perdendo poder de feedback rápido.

Uma forma simples de lembrar: cobertura útil é a que muda sua decisão. Se um teste falha e você confia que precisa corrigir antes de seguir, ele é útil. Se falha e a reação é “deve ser instabilidade, roda de novo”, ele está perdendo utilidade.

Tipos de cobertura: por que “% de linhas” é insuficiente

Métricas de cobertura tradicionais (linhas, ramos, funções) são sinais, não objetivos. Elas ajudam a encontrar áreas sem testes, mas não medem diretamente a capacidade de detectar defeitos. Para falar de cobertura útil, pense em camadas:

Continue em nosso aplicativo

Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.

ou continue lendo abaixo...
Download App

Baixar o aplicativo

  • Cobertura de comportamento: regras de negócio, validações, cálculos, estados e transições.
  • Cobertura de contrato: entradas/saídas, formatos, códigos de erro, compatibilidade de API, eventos publicados.
  • Cobertura de integração: comunicação com banco, filas, serviços externos (idealmente com dublês confiáveis ou ambientes controlados).
  • Cobertura de risco: cenários de maior impacto (pagamento, autorização, dados sensíveis, faturamento, SLAs).

Você pode ter 95% de linhas cobertas e ainda assim não ter testes para: um arredondamento crítico, uma regra de autorização, um timeout em integração, ou uma migração de schema. Por isso, a pergunta operacional não é “qual a cobertura?”, e sim “qual risco essa cobertura reduz?”.

Risco residual de mudanças: definição prática

Risco residual é o risco que permanece após aplicar controles (testes, revisão, validações, feature flags, monitoramento). Ele depende de dois fatores:

  • Probabilidade de a mudança introduzir um problema que passe pelas barreiras.
  • Impacto caso o problema chegue ao usuário/produção (financeiro, reputação, indisponibilidade, retrabalho, compliance).

Mesmo com boa cobertura útil, o risco residual nunca é zero porque:

  • Nem todo comportamento é testável de forma determinística (concorrência, falhas de rede, carga).
  • Há dependências externas e variabilidade de ambiente.
  • Mudanças podem interagir com partes não previstas (efeitos colaterais).
  • Testes podem estar corretos, mas incompletos (faltam cenários) ou mal especificados (asserts não capturam o problema).

Gerenciar risco residual é aceitar que testes são uma barreira importante, mas não única. A meta é reduzir risco a um nível compatível com o contexto: criticidade do domínio, tolerância a falhas, custo de rollback, frequência de deploy e maturidade operacional.

Como conectar cobertura útil ao risco residual

Uma maneira prática é pensar em “mudança” como unidade de análise (um PR, um conjunto de commits, uma história). Para cada mudança, pergunte:

  • O que pode quebrar? (superfícies afetadas: API, UI, regras, persistência, integrações)
  • Quais testes falhariam se quebrasse? (existem? são confiáveis?)
  • O que não está coberto? (lacunas de cenário, bordas, contratos, migrações)
  • Qual o impacto? (se passar, quanto dói?)

Se a mudança afeta uma área crítica e não existe teste que falhe quando a regra quebra, a cobertura útil para aquela mudança é baixa e o risco residual é alto. Se há testes de contrato e de comportamento bem focados, o risco residual cai — mas ainda pode restar risco de performance, compatibilidade, dados reais, etc.

Indicadores práticos de cobertura útil

Você pode medir cobertura útil com sinais que aproximam “capacidade de detectar regressões” e “confiabilidade do feedback”. Exemplos:

  • Taxa de falhas relevantes: proporção de falhas de teste que realmente indicam defeito (vs. flakiness/ambiente). Se muitas falhas são “falsos alarmes”, a utilidade cai.
  • Tempo de feedback: quanto tempo até um teste útil acusar problema após uma mudança. Se demora horas, a utilidade para o fluxo diário diminui.
  • Cobertura por área de risco: em módulos críticos, qual a presença de testes de contrato e comportamento (não só linhas).
  • Mutação (mutation testing) em pontos críticos: se pequenas alterações artificiais passam nos testes, os asserts estão fracos. Não precisa aplicar em tudo; use como auditoria em módulos de alto risco.
  • Defeitos pós-mudança sem teste correspondente: quando um bug aparece, existe um teste que teria pego? Se não, é um indicador direto de lacuna de cobertura útil.

Esses indicadores são mais acionáveis do que “cobertura total do repositório”, porque apontam onde investir para reduzir risco residual.

Passo a passo: avaliando cobertura útil para uma mudança (checklist operacional)

A seguir, um passo a passo que pode ser aplicado em revisão de PR, planejamento de testes ou antes de liberar uma mudança. A ideia é transformar “cobertura” em uma prática orientada a risco.

1) Classifique a mudança por superfície e criticidade

Liste o que a mudança toca e o que pode ser impactado indiretamente:

  • Superfície: API pública, UI, regra de negócio, persistência, integração, job assíncrono, configuração.
  • Criticidade: alto (pagamentos, autorização, dados), médio (fluxos principais), baixo (texto, layout, refatoração interna sem efeito).

Exemplo: um ajuste em cálculo de desconto é “regra de negócio” e pode ser alta criticidade se afeta receita.

2) Identifique invariantes e contratos afetados

Invariantes são propriedades que devem permanecer verdadeiras. Contratos são expectativas entre componentes. Perguntas úteis:

  • Quais entradas devem ser rejeitadas/aceitas?
  • Quais limites (min/max) e arredondamentos são obrigatórios?
  • Quais códigos de erro e mensagens são parte do contrato?
  • Quais eventos devem ser emitidos e com quais campos?

Escreva isso como uma lista curta. Essa lista vira o “mapa” do que a cobertura útil precisa proteger.

3) Mapeie testes existentes que protegem esses pontos

Para cada item do passo 2, responda: “qual teste falha se eu quebrar isso?”. Se a resposta for “não sei” ou “acho que algum teste pega”, trate como lacuna.

Exemplo: se o contrato de API exige que discount nunca seja negativo, existe um teste que envia um caso de borda e valida o erro? Existe um teste que valida o arredondamento em 2 casas?

4) Avalie a força dos asserts (qualidade do oráculo)

Teste útil tem um “oráculo” forte: asserts que capturam a intenção. Sinais de oráculo fraco:

  • Assert apenas de “não é nulo” ou “status 200” sem validar conteúdo.
  • Snapshot enorme sem foco (difícil saber o que importa).
  • Teste que só verifica que um método foi chamado, mas não o resultado observável.

Melhoria típica: trocar asserts genéricos por asserts de propriedades e regras.

5) Complete lacunas com testes focados (mínimo necessário)

Crie testes que cubram:

  • Caminho feliz (para garantir o fluxo principal).
  • Bordas (limites numéricos, strings vazias, datas, timezone).
  • Erros esperados (validações, autorização, recursos inexistentes).
  • Compatibilidade (não quebrar clientes: campos opcionais, defaults).

Exemplo de teste de regra de negócio (pseudo-código):

describe("calculo de desconto") {  it("aplica 10% para cliente VIP") {    total = 200.00    cliente = VIP    desconto = calcularDesconto(total, cliente)    assertEqual(desconto, 20.00)  }  it("nunca retorna desconto negativo") {    total = 0.00    cliente = VIP    desconto = calcularDesconto(total, cliente)    assertTrue(desconto >= 0.00)  }  it("arredonda para 2 casas") {    total = 199.99    cliente = VIP    desconto = calcularDesconto(total, cliente)    assertEqual(desconto, 20.00) // regra de arredondamento definida  }}

Note que esses testes não precisam conhecer detalhes internos; eles validam comportamento.

6) Reduza flakiness e dependências instáveis

Se o teste é instável, ele não reduz risco; ele adiciona ruído. Ações comuns:

  • Congelar tempo (clock fake) para testes com datas.
  • Evitar dependência de ordem (dados isolados por teste).
  • Substituir integrações externas por dublês confiáveis (mock/stub) quando o objetivo é validar regra local.
  • Separar testes de integração reais em um conjunto controlado, com dados e ambiente previsíveis.

O objetivo é que uma falha indique “problema real” com alta probabilidade.

7) Estime risco residual e defina controles adicionais

Depois de ajustar a cobertura útil, ainda pode sobrar risco. Classifique o risco residual em baixo/médio/alto e escolha controles adicionais proporcionais:

  • Feature flag para ativação gradual.
  • Canary release (pequena porcentagem de tráfego).
  • Validações em runtime (asserts defensivos, checagens de invariantes).
  • Monitoramento específico (métricas e alertas para o comportamento alterado).
  • Plano de rollback (reversão rápida e segura).

Exemplo: se a mudança altera cálculo de preço, mesmo com testes fortes, adicione monitoramento de “preço médio por pedido” e “taxa de erro de checkout” para detectar desvios rapidamente.

Exemplo completo: mudança em API com risco de compatibilidade

Cenário: você precisa alterar o endpoint GET /orders/{id} para incluir um novo campo deliveryEstimate e, ao mesmo tempo, corrigir uma regra que antes retornava status incorreto em pedidos cancelados.

Riscos:

  • Clientes antigos podem quebrar se o formato mudar de forma incompatível.
  • A correção de regra pode afetar relatórios e integrações.

Cobertura útil desejada:

  • Teste de contrato: resposta contém campos existentes com o mesmo tipo e sem remoções.
  • Teste de comportamento: pedido cancelado retorna status=CANCELLED em todos os casos relevantes.
  • Teste de borda: pedido inexistente retorna 404 com payload esperado.

Testes focados (pseudo-código):

it("mantem compatibilidade do contrato") {  resp = GET("/orders/123")  assertEqual(resp.status, 200)  assertHasField(resp.body, "id")  assertHasField(resp.body, "status")  assertType(resp.body.status, "string")  // novo campo pode ser opcional para compatibilidade  assertOptionalField(resp.body, "deliveryEstimate")}it("pedido cancelado retorna status CANCELLED") {  seedOrder(id=200, cancelledAt="2026-01-10")  resp = GET("/orders/200")  assertEqual(resp.body.status, "CANCELLED")}it("pedido inexistente retorna 404") {  resp = GET("/orders/999999")  assertEqual(resp.status, 404)  assertEqual(resp.body.errorCode, "ORDER_NOT_FOUND")}

Risco residual (ainda pode existir):

  • Clientes que fazem parsing rígido podem falhar com campo novo (mesmo sendo opcional).
  • Dados reais podem ter combinações de estados não cobertas no seed.

Controles adicionais:

  • Feature flag para expor deliveryEstimate gradualmente.
  • Monitorar aumento de 4xx/5xx no endpoint e erros de parsing reportados.

Como transformar isso em métrica de processo: “risco residual por mudança”

Para usar em gestão e melhoria contínua, você pode criar um indicador simples por mudança (PR) com base em um formulário leve (manual no começo, automatizável depois). Exemplo de escala:

  • Exposição: baixa/média/alta (quantos usuários/sistemas são afetados).
  • Criticidade: baixa/média/alta (impacto se falhar).
  • Cobertura útil presente: baixa/média/alta (existem testes de comportamento/contrato para os pontos alterados, com asserts fortes e baixa flakiness).
  • Controles adicionais: nenhum / monitoramento / flag+canary / rollback testado.

Uma regra prática para estimar risco residual:

risco_residual = (exposicao * criticidade) - mitigacao_por_cobertura - mitigacao_por_controles

Você não precisa de matemática precisa; precisa de consistência para comparar mudanças e priorizar melhorias. O valor está em identificar padrões: “mudanças de alta criticidade estão indo com cobertura útil baixa?” ou “quais módulos sempre exigem canary porque testes não pegam regressões?”.

Armadilhas comuns e como evitá-las

  • Perseguir 100% de linhas: pode gerar testes artificiais e acoplados. Prefira metas por risco (módulos críticos) e por comportamento.
  • Testar detalhes internos: aumenta custo de manutenção e reduz utilidade em refatorações. Foque em contratos e resultados observáveis.
  • Ignorar bordas: muitos incidentes vêm de valores extremos, estados raros e combinações. Inclua bordas como parte do “mínimo aceitável”.
  • Suite lenta: testes úteis precisam rodar com frequência. Separe camadas (rápidos no PR, integrações controladas em pipeline) e otimize gargalos.
  • Flakiness tolerada: se o time aprende a ignorar falhas, a cobertura deixa de ser barreira. Trate flakiness como defeito de engenharia.

Passo a passo: aumentando cobertura útil em um módulo crítico (plano de 2 semanas)

Quando um módulo é recorrente em incidentes ou mudanças arriscadas, um plano curto e objetivo ajuda a elevar a cobertura útil sem “retestar o mundo”.

Dia 1-2: inventário de riscos e contratos

  • Liste as principais responsabilidades do módulo (3 a 7 itens).
  • Para cada responsabilidade, defina invariantes e contratos (entradas/saídas, erros, integrações).
  • Escolha 5 a 10 cenários críticos (incluindo bordas) que representam maior impacto.

Dia 3-5: auditoria de testes existentes

  • Mapeie quais cenários já têm teste e quais não têm.
  • Identifique testes frágeis (muitos mocks, asserts genéricos, dependência de tempo/ordem).
  • Marque flakiness: quais testes falham intermitentemente e por quê.

Dia 6-10: implementação de testes úteis

  • Crie testes de comportamento para os cenários críticos sem cobertura.
  • Reforce asserts fracos (valide propriedades e regras, não apenas status).
  • Introduza dublês confiáveis para dependências externas quando o objetivo for validar regra local.
  • Se necessário, crie um pequeno conjunto de testes de integração controlados para validar contratos com banco/filas.

Dia 11-12: estabilização e tempo de execução

  • Elimine flakiness (clock fake, isolamento de dados, retries apenas onde fizer sentido e com telemetria).
  • Meça tempo de execução e paralelize/otimize o que for gargalo.

Dia 13-14: amarre com risco residual

  • Defina quais mudanças nesse módulo exigem controles adicionais (ex.: sempre usar flag em alterações de preço).
  • Crie um checklist de PR específico do módulo: invariantes, contratos, cenários de borda, monitoramento.
  • Escolha 1 ou 2 métricas de acompanhamento: falhas relevantes vs. flaky, e “bugs pós-mudança sem teste correspondente”.

Esse plano cria um ciclo: você melhora cobertura útil onde dói mais e reduz risco residual de mudanças futuras, sem depender de metas genéricas de cobertura.

Agora responda o exercício sobre o conteúdo:

Ao avaliar uma mudança de alta criticidade, qual situação indica baixa cobertura útil e maior risco residual?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Cobertura util e a que protege comportamento, contratos e cenarios criticos com testes estaveis. Se nao ha teste que falhe ao quebrar algo relevante, ou se ha muita flakiness, a confianca cai e o risco residual aumenta.

Próximo capitúlo

SLOs e métricas de confiabilidade percebida pelo usuário

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.