Cobertura de código explicada: cobertura de linha versus filial e o que 100% realmente significa

  1. Início
  2. Blog
  3. Teste de Software

Teste de software

Cobertura de código explicada: Cobertura de linha versus filial e o que 100% realmente significa

A cobertura informa qual código seus testes executaram. Não informa se os testes verificaram alguma coisa. Medimos um pequeno módulo de três maneiras para mostrar a diferença.

8 min de leitura
Cobertura de código explicada: cobertura de linha versus filial e o que 100% realmente significa

Principais pontos

  • Cobertura de código mede quanto do seu código é executado durante os testes.
  • A cobertura de filiais é mais rigorosa do que a cobertura de linhas e é mais útil.
  • Em nosso teste, um pacote sem sem asserções ainda atingiu 100% de cobertura.
  • Use a cobertura para encontrar código não testado, não como prova de qualidade.

Uma equipe relata com orgulho uma cobertura de código de 95% e, em seguida, envia um bug em uma linha que foi “coberta”. Isto não é uma contradição. A cobertura responde a uma pergunta restrita: os testes executaram esse código? Se eles verificaram o resultado é uma questão totalmente diferente.

Usamos Python com cobertura.py 7.17 e pytest 9.2. Cada número e relatório abaixo são de nossa própria autoria. As ideias se aplicam em qualquer idioma.

O que é cobertura de código?

A cobertura do código é a porcentagem do seu código que é executada enquanto os testes automatizados são executados. Uma ferramenta registra quais linhas e, opcionalmente, quais ramificações foram executadas e, em seguida, relata o que foi perdido.

É mais útil ao contrário. A baixa cobertura em uma área indica, com certeza, que os testes não a estão exercendo. A alta cobertura diz muito menos.

Tipos de cobertura

Tipo Pergunta que responde
Função Cada função foi chamada pelo menos uma vez?
Linha (declaração) Cada linha foi executada pelo menos uma vez?
Filial Cada if foi para os dois lados, verdadeiro e falso?
Condição Cada parte de uma condição combinada (a and b) assumiu os dois valores?
MC/DC Cada condição alterou independentemente o resultado? Usado em software crítico de segurança.

Cada nível é mais rigoroso que o anterior. A maioria das equipes rastreia a cobertura de linhas e filiais. A cobertura de condições e MC/DC aparecem nos padrões de aviação e automotivo, onde o suporte de ferramentas é especializado.

O código que medimos

def shipping_fee(total, is_member, express=False):
    if total < 0:
        raise ValueError("total cannot be negative")
    fee = 0 if total >= 1000 else 50
    if is_member:
        fee = max(fee - 25, 0)
    if express:
        fee += 100
    return fee

Nosso primeiro arquivo de teste teve dois testes:

def test_regular_order():
    assert shipping_fee(500, False) == 50

def test_member_express():
    assert shipping_fee(500, True, express=True) == 125

Executamos com a medição de ramificação ativada:

coverage run --branch -m pytest test_shipping.py
coverage report -m

Lendo o relatório

Name          Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------------
shipping.py       9      1      6      1    87%   3
  • Stmts 9, Miss 1: nove instruções executáveis, uma nunca foi executada.
  • Branch 6, BrPart 1: três if decisões fornecem seis resultados possíveis. Uma decisão só foi para um lado.
  • Faltando 3: linha 3, a raise ValueError, nunca foi executada. Nenhum teste passou com total negativo.

A correção é um teste para o caminho do erro. Também adicionamos um teste para um pedido grande:

def test_free_over_1000():
    assert shipping_fee(1000, False) == 0

def test_negative_total():
    with pytest.raises(ValueError):
        shipping_fee(-1, False)

Com ambos os arquivos, o relatório mostrou 100% e nenhuma linha faltante ou ramificação parcial. Os caminhos de erro são as descobertas clássicas de cobertura de lacunas, e é por isso que os testes para entradas inválidas, projetados com técnicas como particionamento de equivalência, combinam tão bem com eles.

O que o relatório escondeu

Veja o primeiro relatório novamente. Ele disse que apenas a linha 3 estava faltando. Mas os dois primeiros testes usaram um total de 500, então a regra de frete grátis para pedidos de 1.000 ou mais nunca foi testada, e o relatório não a sinalizou.

A razão é que fee = 0 if total >= 1000 else 50 fica em uma linha. cobertura.py mede ramificações entre linhas, portanto, uma expressão condicional dentro de uma única linha conta como uma instrução executada. Se essa regra tivesse um bug, como > em vez de >=, o primeiro conjunto ainda teria mostrado 87% sem nenhuma dica. Os testes do próprio limite, conforme descrito em análise de valor de limite, são o que o detecta.

100% de cobertura sem cheques

A seguir, escrevemos um teste deliberadamente ruim que chama todos os caminhos, mas não afirma nada:

def test_runs_everything_but_checks_nothing():
    shipping_fee(500, True, express=True)
    shipping_fee(1000, False)
    shipping_fee(500, False)
    try:
        shipping_fee(-1, False)
    except ValueError:
        pass
shipping.py       9      0      6      0   100%

Atingiu 100% de cobertura de linhas e ramais. A função poderia retornar qualquer número e esse teste ainda seria aprovado. Este é o limite central da cobertura: mede a execução, não a verificação.

Dica: Ao revisar uma solicitação pull, leia as afirmações, não apenas a mudança de cobertura. Um teste que acrescenta cobertura sem uma afirmação significativa apenas acrescenta falsa confiança.

Para avaliar se os testes realmente detectam bugs, use testes de mutação. Uma ferramenta como mutmut para Python, PIT para Java ou Stryker para JavaScript faz pequenas alterações em seu código, como inverter >= para >, e verifica se um teste falha. Nosso teste sem confirmação permitiria que todas as mutações sobrevivessem.

Qual meta definir

Não existe um número universalmente correto. Orientação prática:

  • Não persiga 100%. Os últimos por cento geralmente significam testar código trivial ou escrever testes que apenas toquem em linhas.
  • Muitas equipes almejam cerca de 70 a 85% no geral, com expectativas mais altas para a lógica de negócios principal.
  • Observe a tendência, não o número. A cobertura que cai a cada lançamento é um sinal de alerta.
  • Exigir cobertura de novo código em solicitações pull, em vez de forçar a ativação do código antigo de uma só vez.
  • Exclua o que não importa, como o código gerado, com regras claras.

Aplica-se a lei de Goodhart: quando uma medida se torna uma meta, deixa de ser uma boa medida. Alvos difíceis incentivam testes como nosso exemplo sem afirmação.

Ferramentas por idioma

Idioma Ferramentas comuns
Pitão cobertura.py, pytest-cov
JavaScript / TypeScript Istambul (Nova York), integrada em Jest e Vitest
Java JaCoCo
.NET Colcha
Vá vá testar -capa
Relatórios Codecov, SonarQube, Macacões

Cobertura em CI

Execute a cobertura como parte do seu pipeline de CI e publique o relatório para que os revisores possam ver quais novas linhas não foram testadas. Com cobertura.py, coverage report --fail-under=80 falha na construção abaixo de um limite. Mantenha qualquer limite modesto e combine-o com uma regra sobre novo código, para que um único arquivo legado grande não bloqueie todas as alterações.

Além da cobertura: melhores perguntas a serem feitas

  • Os testes cobrem as partes arriscadas e frequentemente alteradas do código?
  • Os limites e caminhos de erro são testados, e não apenas o caminho feliz?
  • Um teste falharia se esta linha estivesse errada?
  • Os testes são rápidos e estáveis ​​o suficiente para serem executados em todas as alterações?

Eles pertencem a qualquer estratégia de teste. A cobertura ajuda a responder apenas à primeira.

Excluindo código e lendo relatórios HTML

Não vale a pena testar alguns códigos diretamente, como auxiliares de depuração ou um bloco que só é executado quando um script é iniciado manualmente. cobertura.py permite excluir linhas com um comentário:

if __name__ == "__main__":  # pragma: no cover
    main()

Você também pode definir exclusões e as pastas de origem para medir na configuração, por exemplo em pyproject.toml:

[tool.coverage.run]
branch = true
source = ["shop"]

[tool.coverage.report]
show_missing = true
exclude_also = ["if TYPE_CHECKING:"]

Use exclusões com moderação e revise-as como um código. Cada uma é uma decisão de que algumas linhas nunca serão verificadas. Excluir um manipulador de erros difícil de testar apenas para aumentar o número anula o propósito.

Para o trabalho diário, o relatório HTML é mais útil que a tabela de terminal. Execute coverage html e abra htmlcov/index.html. Cada arquivo é mostrado com linhas executadas em verde, linhas perdidas em vermelho e ramificações parciais em amarelo, para que você possa ver rapidamente qual if nunca seguiu o outro caminho. Muitos editores também podem mostrar cobertura na margem, o que torna as lacunas visíveis enquanto você escreve o código, e não depois.

Cobertura antes da refatoração

A cobertura também é uma verificação de segurança antes da refatoração. Se você planeja reestruturar um módulo, consulte primeiro seu relatório. As áreas vermelhas não têm testes para avisar se o comportamento mudar, portanto, adicione testes de caracterização que capturem o comportamento atual antes de começar. Utilizada desta forma, a cobertura deixa de ser uma meta e passa a ser uma ferramenta prática de planejamento.

O mesmo relatório ajuda quando um bug escapa para a produção. Encontre a linha onde o bug estava e verifique se algum teste o executou. Caso contrário, você encontrou uma lacuna que vale a pena preencher. Se um teste o executou, o problema é uma afirmação fraca, que é exatamente a situação que nosso exemplo sem afirmação mostrou. Ambas as respostas mostram como melhorar o conjunto.

Considerações finais

A cobertura do código é uma lanterna, não uma nota. Use a cobertura de ramificação para encontrar código que nenhum teste toca, especialmente tratamento de erros, e então escreva testes que façam afirmações reais. Se você se lembra de um resultado da nossa corrida, escreva este: 100% de cobertura foi alcançada por um teste que não verificou nada.

Perguntas frequentes

O que é cobertura de código em testes?

É a porcentagem de código executado durante a execução de testes automatizados, medida por linhas, ramificações ou condições. Mostra o que os testes tocaram, não se eles verificaram.

Qual é a diferença entre cobertura de linha e filial?

A cobertura da linha verifica se cada linha funcionou. A cobertura da filial verifica se cada decisão ocorreu em ambos os sentidos. A cobertura de filiais encontra caminhos não testados que a cobertura de linha deixa passar.

A cobertura de 100% do código é boa?

Não por si só. Em nosso teste, uma suíte sem asserções atingiu 100%. Busque testes significativos em códigos importantes, em vez de um número perfeito.

Qual é uma boa porcentagem de cobertura de código?

Muitas equipes visam cerca de 70 a 85% no geral, com mais atenção à lógica crítica. A tendência e a qualidade das afirmações são mais importantes do que o número exato.

Qual a diferença entre o teste de mutação e a cobertura?

O teste de mutação altera ligeiramente seu código e verifica se um teste falhou. Mede se os testes detectam bugs, o que a cobertura não pode fazer.

Continue aprendendo

Trabalhe com Stack Quora

Precisa de ajuda com testes, WordPress ou SEO?

Ajudamos as equipes a fornecer software melhor e a expandir seus sites. Conte-nos no que você está trabalhando e sugeriremos um próximo passo claro.

Continue lendo

Mais guias sobre testes, IA e desenvolvimento moderno

Gostou deste artigo? Escolha um tópico e vá direto para nossos tutoriais mais recentes.

Inteligência artificial

Explicação das alucinações de IA: por que os modelos de linguagem inventam as coisas e como reduzi-las

Explicação das alucinações de IA: o que são, por que os modelos de linguagem as produzem, tipos comuns, como reduzi-las com aterramento e instruções…

Leia o artigo →
DevOps

Variáveis de ambiente explicadas: arquivos .env, segredos e configuração bem feitos

Variáveis de ambiente explicadas com exemplos de Python que executamos: leitura e configuração, por que cada valor é uma string, arquivos .env, hera…

Leia o artigo →
Desenvolvimento de software

Explicação da limitação de taxa de API: algoritmos, cabeçalhos e tratamento de erros 429

Limitação de taxa de API explicada com uma simulação que executamos: token bucket, janela fixa, janela deslizante, 429 respostas, cabeçalhos de limi…

Leia o artigo →
Automação de testes

Zombando em Python: unittest.mock, patch e os erros que fazem os testes mentirem

Zombaria em Python com unittest.mock, testado: Mock e return_value, side_effect, patch onde um nome é usado, especificação e autospec, zombaria de d…

Leia o artigo →
Teste de software

Cobertura de código explicada: cobertura de linha versus filial e o que 100% realmente significa

Cobertura de código explicada com uma execução cobertura.py real: cobertura de linha, ramal e condição, leitura de um relatório, por que 100% de cob…

Leia o artigo →
Bancos de dados e SQL

DELETE vs TRUNCATE vs DROP em SQL: diferenças, reversão e quando usar cada um

DELETE vs TRUNCATE vs DROP explicado com SQL testado: o que cada um remove, rollback, aciona, redefinição de identidade, velocidade e como PostgreSQ…

Leia o artigo →
Bancos de dados e SQL

Exibições SQL explicadas: crie, use e evite as armadilhas, com exemplos

Visualizações SQL explicadas com consultas que executamos: criação de uma visualização, resultados ao vivo, ocultação de colunas, desempenho, visual…

Leia o artigo →
Desenvolvimento de software

Interface TypeScript vs tipo: diferenças reais, testadas com o compilador

Interface TypeScript versus tipo, verificada com o compilador real: o que ambos podem fazer, o que apenas um pode fazer, mesclagem de declarações, e…

Leia o artigo →
Desenvolvimento de software

Python *args e **kwargs explicados: funções flexíveis com exemplos claros

Python *args e **kwargs explicados com o código que executamos: empacotamento e descompactação, ordem dos parâmetros, argumentos somente de palavras…

Leia o artigo →
Desenvolvimento de software

Funções Python Lambda: sintaxe, usos reais e quando não usá-los

Funções lambda do Python explicadas com o código que executamos: sintaxe, classificação com chave, mapa e filtro, fechamentos, armadilha de ligação …

Leia o artigo →
Inteligência artificial

O que são agentes de IA? Como eles funcionam, com um loop de agente simples que você pode executar

O que são agentes de IA? Um guia simples para o loop do agente, ferramentas, memória e proteções, com um pequeno loop Python que executamos, riscos …

Leia o artigo →
DevOps

Monitoramento versus observabilidade: a verdadeira diferença, com logs, métricas e rastreamentos

Explicação do monitoramento versus observabilidade: falhas conhecidas e desconhecidas, logs, métricas e rastreamentos, um passo a passo do incidente…

Leia o artigo →
DevOps

Docker Volumes vs Bind Mounts: persista os dados do contêiner da maneira certa

Volumes Docker versus montagens de ligação explicadas: por que os dados do contêiner desaparecem, volumes nomeados, montagens de ligação, tmpfs, exe…

Leia o artigo →
Automação de testes

TDD vs BDD: principais diferenças, um recurso criado de ambas as maneiras e quando usar cada um

TDD vs BDD explicado com um recurso de desconto construído em ambos os sentidos: refatoração vermelho-verde com o código que executamos, cenários da…

Leia o artigo →
Desenvolvimento de software

Git merge vs rebase: diferenças, exemplos e quando usar cada um

Git merge vs rebase explicado com saída real do log, uma tabela comparativa, a regra de ouro do rebase e um fluxo de trabalho seguro para equipes.

Leia o artigo →
Desenvolvimento de software

List comprehension em Python: sintaxe, exemplos e quando evitar

List comprehension em Python explicada: sintaxe, filtros, if-else, laços aninhados, tempos medidos e quando um laço for comum é a melhor escolha.

Leia o artigo →
Bancos de dados e SQL

SQL JOIN explicado com exemplos (Inner, Left, Right, Full)

Entenda os joins SQL passo a passo com tabelas e consultas de exemplo: INNER, LEFT, RIGHT, FULL, CROSS e SELF, além de erros comuns e dicas de desem…

Leia o artigo →