Mantendo os testes valiosos: as métricas de cobertura de código são confiáveis?
Quando uma métrica se torna uma meta, ela deixa de ser uma boa métrica. - Charles Goodhart
Ao longo dos anos, a qualidade da base de código e as métricas de qualidade e cobertura para testes são um tópico constante na comunidade de Engenharia de Software. Continuamos tentando encontrar um equilíbrio. Muitos projetos colocam em prática métricas específicas de vários tipos. Vamos tentar mergulhar nas perguntas abaixo:
Devemos definir métricas rígidas?
A cobertura do código indica qualidade?
Como podemos usar métricas de cobertura a nosso favor?
Bora conversar sobre esses pontos!
Lei de Goodhart
Você já ouviu falar na Lei de Goodhart?1 O economista britânico Charles Goodhart afirmou:
“Quando uma métrica se torna uma meta, ela deixa de ser uma boa métrica.”2
Agora, a pergunta é: transformar métricas em metas no contexto da engenharia de software, especificamente para cobertura de código, pode ser prejudicial? A resposta é sim. Deixe-me explicar com um exemplo.
Imagine uma equipe de desenvolvimento trabalhando em uma entrega importante para um grande cliente do setor financeiro. O objetivo é criar um aplicativo corporativo que agilize processos internos críticos da empresa. A equipe de qualidade, junto com os gestores, define uma porcentagem mínima de 97% de cobertura de código para que o projeto seja aprovado no pipeline. A lógica por trás disso é: quanto maior o número de linhas cobertas, menor a chance de bugs. Logo, a principal métrica do projeto passa a ser a cobertura de código.
Após algumas semanas, o que acontece? Muitos recursos retornam com bugs. A equipe de gestão começa a se perguntar:
“Se as métricas estão dentro do esperado, por que tantas funcionalidades apresentam problemas?”
A resposta? Focar apenas em métricas, como a cobertura de código, mascara problemas reais.
O Problema na Prática
Em vez de melhorar a qualidade dos testes e focar em validar comportamentos do sistema, a equipe começa a testar apenas para atingir a métrica estabelecida. O resultado? Testes que não cobrem cenários reais de uso, mas sim linhas de código, cenários de caminho feliz. Esse problema é especialmente crítico em projetos grandes e complexos.
Sem metas baseadas em resultados concretos, objetivos reais, buscar uma alta porcentagem de cobertura de código pode apenas esconder problemas críticos. E o cenário piora com fatores como:
• Má gestão
• Documentação deficiente
• Falta de entendimento do domínio do software
• Squads desalinhados trabalhando juntos
Esse é o ambiente perfeito para:
• Perdas financeiras;
• Insatisfação dos clientes – sejam eles usuários finais ou corporações.
Um Problema Comum
Já trabalhei em projetos onde ninguém sabia como elaborar cenários de teste para cada funcionalidade. O resultado? Testes unitários eram criados com base no “achismo”. Algo como:
• “Acho que é isso que precisamos testar.”
Ou pior:
• “Não precisamos de tantos testes nesse recurso, só cubra as linhas.”
Cobrir apenas o caminho feliz do código é um erro! Precisamos entender o que estamos testando e o impacto disso no negócio. Por isso é importante em reuniões procurar entender as regras de negócio e entender o produto no qual você está trabalhando!
Definir métricas como metas não garante qualidade. Especialmente na engenharia de software, a cobertura de código deve ser vista como um resultado, não uma meta.
Se você é um gerente de projeto, líder técnico, sênior, CTO, aqui vai uma dica: qualidade precisa ser baseada em resultados sólidos! A cobertura de código é apenas um relatório que auxilia no ciclo de desenvolvimento.
Os resultados esperados?
• Funcionalidades COM MENOS bugs (pode ser que algumas estejam livres deles), aprovadas para produção sem retornos ou erros inesperados;
• Aumento de produtividade;
• Maior segurança para refatorar e criar novas funcionalidades, pois os testes são confiáveis.
Cobertura é resultado natural, não meta. Isso é o que garante um software robusto e uma equipe produtiva.
Determinar porcentagens de cobertura é realmente importante?
Essa é uma pergunta interessante, né? Afinal, todo mundo tem uma opinião sobre isso. Confesso que em muitos projetos em que trabalhei, a porcentagem de cobertura era quase sempre uma exigência. Algumas empresas pediam 80% no projeto inteiro. Outras iam além, exigindo mais de 90% para cada classe. E ainda havia aquelas super detalhistas: classes de domínio com regras de negócio precisavam de 100% de cobertura.
Mas e aí, o que dá pra aprender com essas métricas de cobertura de código?
A Importância das Métricas
Primeiro, elas são úteis, mas dentro do contexto certo. Métricas muito rígidas podem gerar problemas como os que discutimos anteriormente. Por outro lado, ter métricas realistas, alinhadas com as necessidades de cada cliente e priorizando qualidade, ajuda a manter um padrão e direciona o projeto no caminho certo.
Agora, vamos ser sinceros: se não houver um requisito mínimo de cobertura, será que os desenvolvedores vão se preocupar com os testes? Na prática, sabemos que não. Muitos desenvolvedores simplesmente não gostam de escrever testes – e eu me incluo nessa lista, alguns atrás odiava escrever testes, especialmente aqueles cenários que pareciam improváveis.
Mas vou te dizer uma coisa: todo desenvolvedor que quer crescer e amadurecer precisa levar os testes a sério. Hoje, vejo que os testes não são apenas uma etapa chata do ciclo de desenvolvimento; eles são fundamentais. Eles trazem confiança, qualidade e até ajudam a identificar problemas antes que o cliente veja.
Porcentagens Realistas
Então, volto à pergunta: ter porcentagens realistas de cobertura de código é importante?
Minha resposta continua sendo sim.
Mas tem um detalhe: essas porcentagens precisam ser alcançáveis, realistas! Pressionar a equipe com metas irrealistas só cria desconforto, muda o foco e atrapalha os prazos. Por outro lado, trabalhar com padrões realistas, definidos junto com especialistas de domínio e gerentes de projeto, pode ser uma estratégia valiosa.
Aliás, tem uma citação que resume bem isso, do livro Unit Testing Principles, Practices, and Patterns, do Vladimir Khorikov:
“É bom ter um alto nível de cobertura nas principais partes do seu sistema. É ruim fazer desse alto nível um requisito. A diferença é sutil, mas crítica.”
Minha Opinião
Vou compartilhar algo que acho importante: vejo mais valor em porcentagens por arquivo ou classe do que em uma meta genérica para o projeto inteiro. Mas calma, tem um porém! Como veremos mais adiante neste artigo, confiar cegamente na cobertura por linha também pode ser perigoso.
Ainda assim, definir porcentagens específicas para classes ou camadas críticas – como aquelas onde as regras de negócio residem – pode trazer vantagens significativas. Por quê? Porque isso força os desenvolvedores a entenderem melhor essas partes mais complexas e críticas para o negócio.
E se tem algo que aprendemos em engenharia de software, é que as classes de domínio geralmente têm mais caminhos, maior complexidade ciclomática e, claro, precisam de mais atenção.
Vamos explorar mais sobre a cobertura de ramificação e por que isso é tão relevante!
Cobertura de Filiais (Branch Coverage)
Em vez de usar o número bruto de linhas de código, essa métrica foca em estruturas de controle, como instruções if e switch. Ela mostra quantas dessas estruturas de controle são atravessadas por pelo menos um teste no conjunto. Veja um exemplo com .NET:
public class Phone
{
public bool isValid(string phone)
{
if (phone == null)
{
return false;
}
if (!phone.All(char.IsDigit))
{
return false;
}
if (phone.Length < 11)
{
return false;
}
if (phone.Length > 13)
{
return false;
}
return true;
}
}
Agora dê uma olhada no relatório gerado. Gostaria que você desse uma boa olhada nas métricas levantadas apenas para este arquivo do número de ramificações:
A imagem acima é um ótimo exemplo de como os relatórios de cobertura de código podem ser úteis. Veja como os branchs não cobertos estão destacados de forma clara, indicando exatamente onde precisamos trabalhar. Essa visualização torna simples identificar o que está faltando – e, nesse caso, praticamente tudo ainda precisa ser coberto!
Cada declaração if ou switch não pode passar despercebida. E se algum detalhe for esquecido? O relatório está ali para apontar e mostrar o que foi deixado de lado. É como ter um assistente que te lembra do que ainda precisa de atenção.
Cobertura de Branch vs. Cobertura de Código
Agora, é importante entender o seguinte: a cobertura de branch é uma parte da cobertura de código. Pense na cobertura de código como um conceito mais amplo, dividido em vários critérios diferentes – sendo a cobertura de branch um deles.
A cobertura de branch é mais especializada, focada em garantir que cada caminho ou decisão no código seja testado. Por exemplo, no caso da imagem, cada condição dentro dos if precisa ser avaliada para saber se já foi testada em todos os cenários possíveis.
O Equilíbrio é Fundamental
Não vou dizer que a cobertura de branch é “melhor” ou “mais útil” do que outros tipos de cobertura. Tudo depende do equilíbrio. A cobertura de linha, por exemplo, anda de mãos dadas com a cobertura de branch. Uma não substitui a outra – elas se complementam.
Então, qual é a recomendação aqui? Sempre preste atenção nos branches não cobertos no seu código. Pergunte-se:
• Por que esse branch não foi coberto?
• Como posso garantir que ele seja testado?
Responder essas perguntas ajuda a criar testes mais robustos. Afinal, nenhum detalhe deve passar batido!
Os problemas de confiar completamente em porcentagens de cobertura de código!
Algumas pessoas acreditam que depois de refatorar um código e o número de linhas na cobertura do código cair, isso irá automaticamente melhorar nossos testes. Isso é ilógico. Aqui está um exemplo:
public class Phone
{
public bool isValid(string phone)
{
if (phone == null) return false;
return phone.All(char.IsDigit) && phone.Length >= 11 && phone.Length <= 13;
}
}
Fiz uma pequena refatoração no código sem alterar nada nos testes unitários. Agora vamos fazer uma comparação, antes e depois da refatoração nas métricas que o framework Coverage nos trouxe:
ANTES DA REFATORAÇÃO
DEPOIS DA REFATORAÇÃO
O número percentual de linhas aumentou – era 46%, mas agora temos 80%. Isso significa que houve uma evolução? Uma melhora? Não!
Nós apenas reduzimos as linhas de código, mas os comportamentos (as ramificações importantes) não estão cobertos pelos testes! Se você observar as imagens de antes e depois, verá que a porcentagem de cobertura de ramificação (os caminhos que o algoritmo pode tomar, ou seja, branch coverage) permanece a mesma, mesmo após a refatoração.
E esse é um dos grandes problemas ao trabalhar com cobertura de código. Se agirmos sem pensar ou analisar com cuidado, podemos facilmente ser enganados!
Manipular números de cobertura pode ser muito fácil. Quanto mais compacto for o seu código, melhor será a métrica de cobertura, pois ela considera apenas o número bruto de linhas. Por isso, é essencial uma análise cuidadosa por parte dos desenvolvedores e testadores de software, sempre observando a qualidade dos testes escritos.
O que isso prova? A cobertura de código não está relacionada à qualidade. Só porque seu código está 100% coberto não significa que seus testes refletem qualidade.
Tudo o que vimos até aqui reforça ainda mais que a cobertura é apenas uma ferramenta, usada para ajudar na criação de testes eficazes e de qualidade. Além disso, um teste precisa ter diversos atributos importantes para ser considerado de qualidade. Por exemplo, ele deve ser:
• Legível;
• Organizado;
• Fácil de entender;
• Livre de dezenas de asserts;
• E, principalmente, focado em testar os comportamentos da funcionalidade.
Então, respondendo à pergunta:
A cobertura de código indica qualidade? Não, com base em tudo o que vimos.
Este tópico está muito relacionado ao próximo, por isso reservei uma seção especial para falar sobre isso. Atingir uma porcentagem de cobertura não deve ser motivo para liberar código para produção.
Não Use a Cobertura de Código como Critério de Liberação
Vamos reforçar isso. Ah e vou te contar algo que vi acontecer pouquíssimas vezes na minha carreira: usar cobertura de código como critério para liberar funcionalidades para produção. E sabe por que é importante falar sobre isso? Porque é um grande erro!
Depender de porcentagens de cobertura para liberar funcionalidades críticas pode ser desastroso. O código precisa passar por diversas verificações de qualidade, incluindo um Code Review rigoroso, feito pelos desenvolvedores do projeto e, se possível, pelo cliente (caso ele tenha uma equipe técnica para isso). É necessário também uma Analista de Qualidade (QA) que consegue ver além e tem a expertise para procurar por outro ângulo, talvez algo que a equipe de desenvolvimento não tenha compreendido ou validado corretamente.
Agora se a única exigência para liberar código for alcançar uma certa porcentagem de cobertura, sinto dizer, mas isso é amadorismo. Grandes corporações precisam de processos robustos e confiáveis.
E aqui vai um alerta: evite dores de cabeça no futuro. Crie uma pipeline confiável e de alta qualidade para o seu software.
Por Que Cobertura Não Deve Ser o Único Critério
Para reforçar a importância de nunca usar métricas de cobertura como único critério de liberação, pense no software com o qual você trabalha hoje. O que aconteceria se a única exigência fosse atingir uma porcentagem de cobertura para liberar um recurso?
No início, talvez fosse simples corrigir os bugs que surgissem pela falta de testes adequados. Mas, à medida que o software cresce, a complexidade aumenta. E adivinhe só: em algum momento, lançar novos recursos se tornará um pesadelo. No pior cenário, isso pode levar ao fim do projeto ou do software.
Aliás, Vladimir Khorikov, no livro Unit Testing Principles, Practices, and Patterns, alerta sobre isso ao dizer:
“Cobertura de código nunca deve ser um objetivo. Ela é apenas um efeito colateral de bons testes. Focar na cobertura pode incentivar práticas ruins, como criar testes que não validam comportamentos.”
Esse pensamento reforça que a métrica de cobertura é apenas uma ferramenta para ajudar no desenvolvimento, e não um objetivo final.
O que Fazer em Vez Disso?
É essencial ter Code Reviews rigorosos e critérios sólidos de aceitação antes de liberar código para homologação ou produção. Esses processos são muito mais eficazes para garantir a qualidade do que confiar cegamente em porcentagens de cobertura.
Martin Fowler, um dos maiores especialistas em engenharia de software, também comenta sobre isso em seu artigo Test Coverage:
“Não é a cobertura de teste que garante qualidade, mas o que você está testando e como. O foco deve ser nos comportamentos e nos cenários críticos, e não em números.”
A ideia aqui é clara: priorize a qualidade dos testes, não apenas o quanto do código está coberto.
Mas calma, esse é um tópico tão importante que merece um post só para ele.
Como Podemos Usar Métricas de Cobertura de Código a Nosso Favor?
Falamos bastante sobre os desafios e armadilhas da cobertura de código, mas como podemos usar essa ferramenta de forma eficaz? Aqui estão algumas sugestões práticas:
1. Foque nas áreas críticas do código
Use métricas de cobertura em áreas mais complexas e críticas para o tipo de negócio em que você está trabalhando. Essas partes do código geralmente têm maior impacto no comportamento do sistema e merecem atenção redobrada.
2. Escreva cenários de teste para arquivos com baixa cobertura
Se a equipe identificar arquivos com cobertura zero ou porcentagens muito baixas de branches cobertos, é uma ótima oportunidade para escrever cenários de teste que validem os comportamentos dessas funcionalidades.
3. Reforce a importância de testar branches de código
Lembre-se: branches representam os caminhos que um algoritmo pode seguir. Estruturas como if/else precisam ser testadas cuidadosamente para garantir que todos os cenários possíveis sejam validados.
4. Não confie cegamente no relatório de cobertura
O relatório é útil, mas não assume que testes de qualidade foram escritos apenas porque a cobertura está alta. Como já discutimos, 100% de cobertura não significa qualidade.
5. Evite estabelecer metas irreais de cobertura
Não é produtivo exigir altos níveis de cobertura de código ou porcentagens elevadas no projeto se a qualidade dos testes continuar sendo insuficiente. Priorize qualidade sobre quantidade.
6. Use relatórios de cobertura para identificar complexidade ciclomática
Ferramentas de cobertura podem revelar a complexidade ciclomática do código, indicando se uma classe ou método precisa ser refatorado. Refatorar pode melhorar a legibilidade e reduzir riscos.
7. Aproveite métricas de cobertura para revisões de código
Métricas de cobertura são ótimas aliadas em Code Reviews, ajudando os revisores a se concentrarem em áreas menos testadas e a identificarem riscos potenciais.
8. Nunca ignore cenários de teste complementares
Revise cuidadosamente os relatórios e porcentagens exibidos, mas lembre-se de que há cenários além do que o relatório mostra. Nunca dependa apenas da métrica para avaliar se a cobertura é suficiente.
Encontre o Ponto de Equilíbrio
No final, cada projeto terá suas próprias demandas, e cabe à equipe determinar os padrões adequados para a cobertura de código. As sugestões acima são lições que aprendi com especialistas e durante minhas experiências em projetos nacionais e internacionais. A chave está no equilíbrio entre métricas e qualidade.
Agradeço muito por acompanhar e ler até o final! Grande abraço e até! 🙂
Não vou me aprofundar tanto agora nesse principio, mas ele é bem profundo e interessante para outras áreas da engenharia de software também!
Nota Explicativa: Quem foi Charles Goodhart e por que sua frase é tão relevante?
Charles Goodhart é um renomado economista britânico, amplamente conhecido por sua contribuição à economia e à formulação de políticas financeiras. Ele desenvolveu a Lei de Goodhart, uma observação poderosa que inicialmente surgiu no contexto econômico, mas que se mostrou aplicável a diversas áreas, incluindo a engenharia de software.
A frase “Quando uma métrica se torna uma meta, ela deixa de ser uma boa métrica” nos alerta sobre os perigos de transformar indicadores em objetivos finais. Em engenharia de software, isso é essencial porque métricas como cobertura de código, tempo de entrega e número de commits não garantem, por si só, a qualidade do software.
Por exemplo, ao transformar a cobertura de código em meta, corremos o risco de criar testes que não avaliam comportamentos reais, apenas para “cumprir” um número. Isso pode levar a resultados enganosos, como uma falsa sensação de segurança, enquanto problemas reais permanecem ocultos.
A Lei de Goodhart nos ensina a usar métricas como ferramentas para direcionar decisões, e não como objetivos absolutos. Essa lição é essencial para evitar más práticas e manter o foco na qualidade e nos resultados reais.
Excelente artigo e mais uma vez me trouxe insights para eu levar pra o time que atuo :)
Estava procurando de fato algo referente a essa questão de cobertura.