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.
- 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
ifdecisõ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
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.
Mais guias sobre testes, IA e desenvolvimento moderno
Gostou deste artigo? Escolha um tópico e vá direto para nossos tutoriais mais recentes.
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 →DevOpsVariá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 softwareExplicaçã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 testesZombando 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 softwareCobertura 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 SQLDELETE 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 SQLExibiçõ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 softwareInterface 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 softwarePython *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 softwareFunçõ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 artificialO 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 →DevOpsMonitoramento 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 →DevOpsDocker 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 testesTDD 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 softwareGit 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 softwareList 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 SQLSQL 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 →