Cobertura de código: a métrica importa realmente
1. O que é cobertura de código e como ela é medida?
Cobertura de código é uma métrica que indica qual porcentagem do código-fonte foi executada durante a execução dos testes. Existem diferentes tipos de cobertura:
- Cobertura de linhas: quantas linhas de código foram executadas
- Cobertura de branches: quantos caminhos condicionais (if/else, switch) foram percorridos
- Cobertura de funções: quantas funções/métodos foram chamados
- Cobertura de caminhos: quantas combinações possíveis de execução foram testadas
Ferramentas populares incluem coverage.py (Python), Istanbul (JavaScript) e JaCoCo (Java). Um relatório básico de cobertura se parece com:
Name Stmts Miss Cover
-------------------------------------------
calculadora.py 20 2 90%
validador_cpf.py 15 5 67%
processador_pagamento.py 30 0 100%
-------------------------------------------
TOTAL 65 7 89%
2. A armadilha da cobertura cega
A cobertura cria uma falsa sensação de segurança. É perfeitamente possível ter 100% de cobertura com testes inúteis. Veja um exemplo real:
# Código sendo testado
def calcular_desconto(valor, cliente_vip):
if cliente_vip:
return valor * 0.9
return valor * 0.95
# Teste que atinge 100% de cobertura mas não valida nada
def test_calcular_desconto():
calcular_desconto(100, True) # Executa, mas não verifica resultado
calcular_desconto(100, False) # Executa, mas não verifica resultado
# Nenhum assert! Cobertura = 100%, teste = inútil
Casos reais mostram equipes celebrando 95% de cobertura enquanto bugs críticos passam despercebidos — porque os testes não verificavam condições de borda, valores negativos ou entradas nulas.
3. Métricas complementares que importam mais
Mutação de código (mutation testing): essa técnica insere pequenas modificações no código (mutantes) e verifica se os testes os detectam. Se um mutante sobrevive, seus testes são frágeis.
# Código original
if idade >= 18:
return "adulto"
# Mutante gerado
if idade > 18: # mudança de >= para >
return "adulto"
# Se o teste não detectar essa mudança, o mutante sobreviveu
Complexidade ciclomática: mede quantos caminhos independentes existem no código. Um módulo com cobertura de 90% mas complexidade 50 tem muito mais risco que um com 70% e complexidade 5.
Taxa de falhas em produção: estudos mostram que correlação entre cobertura alta e baixa taxa de bugs é fraca. O que realmente importa é se os testes cobrem cenários reais de uso.
4. Estratégias para usar cobertura de forma inteligente
Defina metas realistas por módulo:
# Configuração de cobertura por tipo de módulo
Regras sugeridas:
- Lógica de negócio (domínio): 80-90% de cobertura de branches
- Repositórios/DAO: 70-80% de cobertura de linhas
- Interface de usuário: 50-60% (testes E2E complementam)
- Configuração/bootstrapping: 30-40% (código boilerplate)
Na integração contínua, evite metas fixas que bloqueiam deploys. Prefira alertar apenas em quedas drásticas:
# Estratégia de CI: falhar apenas se cobertura cair >5%
# Exemplo com GitHub Actions
- name: Check coverage drop
run: |
CURRENT=85
PREVIOUS=$(cat coverage_history.txt)
if [ $((PREVIOUS - CURRENT)) -gt 5 ]; then
echo "Cobertura caiu mais de 5%!"
exit 1
fi
5. Cobertura em diferentes tipos de teste
Testes unitários, de integração e E2E contribuem de forma diferente para a cobertura:
# Teste unitário: cobre lógica isolada (alta cobertura de branches)
def test_calcular_imposto():
assert calcular_imposto(1000) == 150
# Teste de integração: cobre fluxo entre componentes
def test_fluxo_pedido():
resposta = cliente.post("/pedido", json={"item": "livro"})
assert resposta.status_code == 201
# Teste E2E: cobre caminhos completos (baixa cobertura de código)
def test_compra_completa():
page.goto("https://loja.com")
page.click("button#comprar")
assert page.text_content(".mensagem") == "Compra realizada"
Testes parametrizados com pytest aumentam a cobertura de caminhos sem duplicar código:
import pytest
@pytest.mark.parametrize("idade,esperado", [
(17, "menor"),
(18, "adulto"),
(65, "idoso"),
(-1, "inválido"), # borda
(0, "inválido"), # borda
])
def test_classificar_idade(idade, esperado):
assert classificar_idade(idade) == esperado
6. Mitos comuns sobre cobertura de código
Mito 1: "100% de cobertura = código perfeito" — Falso. Cobertura não mede qualidade das asserções, performance, segurança ou usabilidade.
Mito 2: "Cobertura alta dispensa testes manuais ou de aceitação" — Falso. Testes automatizados não capturam problemas de UX, fluxos visuais ou requisitos não funcionais.
Mito 3: "Só times iniciantes se preocupam com cobertura" — Falso. Empresas como Google e Microsoft usam cobertura como métrica auxiliar, mas combinam com code review, testes de mutação e análise estática.
7. Quando a cobertura realmente importa
Em projetos com requisitos críticos, a cobertura funciona como rede de proteção:
- Saúde e finanças: sistemas de prescrição médica, processamento de pagamentos
- Código legado em refatoração: cobertura alta permite refatorar com confiança
- Equipes grandes: cobertura funciona como padrão mínimo de qualidade e comunicação entre desenvolvedores
# Exemplo: refatoração segura com cobertura
# Antes (cobertura 90%)
def processar_transacao(valor, tipo):
if tipo == "credito":
return valor + taxa
elif tipo == "debito":
return valor - taxa
# Depois da refatoração (cobertura mantida em 90%)
def processar_transacao(valor, tipo):
operacoes = {"credito": lambda v: v + taxa, "debito": lambda v: v - taxa}
return operacoes.get(tipo, lambda v: 0)(valor)
8. Conclusão prática: cobertura como guia, não como deus
Cobertura de código é uma métrica útil, mas insuficiente sozinha. Ela responde "o código foi executado?", mas não "o código foi testado adequadamente?".
Recomendações finais:
- Combine métricas: cobertura + mutação + complexidade ciclomática
- Automatize a medição: gere relatórios em cada PR e revise periodicamente
- Foque em branches, não apenas linhas: 80% de cobertura de branches vale mais que 95% de linhas
- Use cobertura para identificar código não testado, não como meta de desempenho
- Invista em testes de mutação para validar a qualidade real dos testes
Lembre-se: cobertura alta com testes fracos é pior que cobertura média com testes robustos. A métrica deve guiar, não ditar as regras.
Referências
- coverage.py — Documentação Oficial — Guia completo sobre medição de cobertura de código em Python, incluindo configuração de branches e exclusões
- Mutation Testing with Pytest (Mutmut) — Tutorial prático de teste de mutação para complementar a cobertura tradicional
- Istanbul — JavaScript Code Coverage Tool — Documentação oficial da ferramenta de cobertura para JavaScript/TypeScript
- JaCoCo — Java Code Coverage Library — Referência para medição de cobertura em projetos Java com exemplos de integração Maven/Gradle
- Martin Fowler — Code Coverage — Artigo clássico do autor sobre os limites e usos inteligentes da métrica de cobertura
- Google Testing Blog — Code Coverage Best Practices — Práticas recomendadas pelo Google para uso de cobertura em larga escala