A cobertura do teste é uma medida adequada da qualidade do código?

20

Se eu tiver algum código que tenha 80% de cobertura de teste (todos os testes passam), é justo dizer que é de qualidade superior ao código sem cobertura de teste?

Ou é justo dizer que é mais sustentável?

David_001
fonte
2
100% de cobertura não significa que foi bem testado. Mas 0% significa que ainda não foi testado.
Mouviciel 26/10/10
1
Tecnicamente não. Praticamente sim. A experiência ensinou a muitos engenheiros e testadores de software que, quando a cobertura do código atinge cerca de 80%, os tipos de defeitos para os quais o teste de unidade é adequado começam a se estabilizar. É o princípio do pareto. Basicamente, quando você chega ao ponto em que está cobrindo 80% do código, independentemente da qualidade de seus testes, provavelmente já testou os 20% do código que causa a maioria dos possíveis problemas de maneira bastante completa. Isso não é absoluto, mas sim uma sabedoria convencional. Você precisa ser mais cuidadoso se a vida depender de seus testes.
Calphool
@JoeRounceville Não tenho certeza ... Consigo obter alta cobertura de teste sem testar nada realmente útil. A cobertura apenas informa quanto do código está sendo tocado pelo conjunto de testes, e não se os testes são significativos.
Andrés F.
1
@AndresF. Por isso disse "tecnicamente não, praticamente sim". As pessoas não são idiotas (geralmente). Eles (geralmente) não testam apenas casos simples. Portanto, com base na experiência , muitas lojas param em torno de 80% de cobertura, assumindo (razoavelmente seguro) que seu pessoal não é idiota.
Calphool

Respostas:

24

Em sentido estrito, não é justo fazer reivindicações até que a qualidade do conjunto de testes seja estabelecida. A aprovação em 100% dos testes não é significativa se a maioria dos testes for trivial ou repetitiva entre si.

A questão é: Na história do projeto, algum desses testes descobriu bugs? O objetivo de um teste é encontrar erros. E se não o fizeram, falharam como testes. Em vez de melhorar a qualidade do código, eles podem estar apenas fornecendo uma falsa sensação de segurança.

Para aprimorar seus projetos de teste, você pode usar (1) técnicas de caixa branca, (2) técnicas de caixa preta e (3) teste de mutação.

(1) Aqui estão algumas boas técnicas de caixa branca para aplicar aos seus projetos de teste. Um teste de caixa branca é construído com código-fonte específico em mente. Um aspecto importante do teste da caixa branca é a cobertura do código:

  • Toda função é chamada? [Cobertura funcional]
  • Toda declaração é executada? [Cobertura de declarações - A cobertura funcional e a cobertura de declarações são muito básicas, mas são melhores que nada]
  • Para cada decisão (como ifou while), você tem um teste que o força a ser verdadeiro e outro que o força a ser falso? [Cobertura de decisão]
  • Para cada condição que é uma conjunção (usos &&) ou disjunção (usos ||), cada subexpressão tem um teste em que é verdadeiro / falso? [Cobertura da condição]
  • Cobertura de loop: Você tem um teste que força 0 iterações, 1 iteração, 2 iterações?
  • É cada break um de um loop é coberto?

(2) As técnicas de caixa preta são usadas quando os requisitos estão disponíveis, mas o código em si não é. Isso pode levar a testes de alta qualidade:

  • Seus testes de caixa preta cobrem várias metas de teste? Você deseja que seus testes sejam "gordos": eles não apenas testam o recurso X, mas também Y e Z. A interação de diferentes recursos é uma ótima maneira de encontrar bugs.
  • O único caso em que você não deseja testes "gordos" é quando está testando uma condição de erro. Por exemplo, testando entrada de usuário inválida. Se você tentou atingir várias metas inválidas de teste de entrada (por exemplo, um CEP inválido e um endereço inválido), é provável que um caso esteja ocultando o outro.
  • Considere os tipos de entrada e forme uma "classe de equivalência" para os tipos de entrada. Por exemplo, se seu código testar para ver se um triângulo é equilátero, o teste que usa um triângulo com lados (1, 1, 1) provavelmente encontrará os mesmos tipos de erros que os dados de teste (2, 2, 2) e (3, 3, 3) encontrará. É melhor gastar seu tempo pensando em outras classes de informações. Por exemplo, se seu programa lida com impostos, convém fazer um teste para cada faixa de imposto. [Isso é chamado de particionamento de equivalência.]
  • Casos especiais são frequentemente associados a defeitos. Seus dados de teste também devem ter valores de limite, como aqueles acima, acima ou abaixo das bordas de uma tarefa de equivalência. Por exemplo, ao testar um algoritmo de classificação, você desejará testar com uma matriz vazia, uma matriz de elemento único, uma matriz com dois elementos e, em seguida, uma matriz muito grande. Você deve considerar casos de fronteira não apenas para entrada, mas também para saída. [Esta é uma análise de valor-limite de chamadas.]
  • Outra técnica é "Erro de adivinhação". Você sente que, se tentar alguma combinação especial, poderá interromper o programa? Então tente! Lembre-se: seu objetivo é encontrar erros, não confirmar que o programa é válido . Algumas pessoas têm o dom de adivinhar erros.

(3) Por fim, suponha que você já tenha muitos testes agradáveis ​​para cobertura de caixa branca e técnicas aplicadas de caixa preta. O que mais você pode fazer? É hora de testar seus testes . Uma técnica que você pode usar é o Teste de Mutação.

Sob teste de mutação, você faz uma modificação em (uma cópia) do seu programa, na esperança de criar um bug. Uma mutação pode ser:

Alterar uma referência de uma variável para outra variável; Inserir a função abs (); Alterar menor que para maior que; Excluir uma declaração; Substitua uma variável por uma constante; Excluir um método de substituição; Exclua uma referência a um super método; Alterar ordem dos argumentos

Crie várias dúzias de mutantes, em vários locais do seu programa [o programa ainda precisará ser compilado para testar]. Se seus testes não encontrarem esses erros, agora você precisará escrever um teste que possa encontrar o erro na versão mutada do seu programa. Depois que um teste encontra o bug, você mata o mutante e pode tentar outro.


Adendo : Esqueci de mencionar este efeito: os bugs tendem a se agrupar . O que isso significa é que quanto mais erros você encontrar em um módulo, maior a probabilidade de encontrar mais erros. Portanto, se você tem um teste que falha (ou seja, o teste é bem-sucedido, já que o objetivo é encontrar erros), não apenas você deve corrigi-lo, mas também deve escrever mais testes para o módulo, usando o comando técnicas acima.

Enquanto você encontrar bugs a uma taxa constante, os esforços de teste devem continuar. Somente quando houver um declínio na taxa de novos bugs encontrados, você deve ter certeza de que fez bons esforços de teste para essa fase de desenvolvimento.

Macneil
fonte
7

Por uma definição, é mais sustentável, já que qualquer mudança de quebra é mais provável de ser detectada pelos testes.

No entanto, o fato de o código passar nos testes de unidade não significa que seja intrinsecamente de maior qualidade. O código ainda pode estar mal formatado com comentários irrelevantes e estruturas de dados inadequadas, mas ainda pode passar nos testes.

Eu sei qual código eu preferiria manter e estender.

ChrisF
fonte
7

Código com absolutamente nenhum teste pode ser de altíssima qualidade, legível, bonito e eficiente (ou total de lixo), portanto, não é justo dizer que o código com 80% de cobertura de teste é de qualidade superior ao código sem cobertura de teste.

Pode ser justo dizer que o código de 80% coberto com bons testes é provavelmente de qualidade aceitável e provavelmente relativamente de manutenção. Mas isso garante pouco, na verdade.

Joonas Pulakka
fonte
3

Eu chamaria isso de mais refatorável. A refatoração fica extremamente fácil se o código for coberto com muitos testes.

Seria justo chamá-lo de mais sustentável.

Josip Medved
fonte
2

Eu concordaria sobre a parte de manutenção. Michael Feathers postou recentemente um vídeo de uma excelente palestra chamada " A profunda sinergia entre testabilidade e bom design ", na qual ele discute esse tópico. Na palestra, ele diz que o relacionamento é uma maneira, isto é, código bem projetado é testável, mas código testável não é necessariamente bem projetado.

Vale a pena notar que o streaming de vídeo não é ótimo no vídeo; portanto, pode valer a pena fazer o download se você quiser assistir na íntegra.

Paddyslacker
fonte
-2

Eu já me faço essa pergunta há algum tempo em relação à "cobertura da condição". Então, que tal esta página em atollic.com "Por que a análise de cobertura de código?"

Mais tecnicamente, a análise de cobertura de código encontra áreas no seu programa que não são cobertas pelos seus casos de teste, permitindo que você crie testes adicionais que cobrem partes não testadas do seu programa. Portanto, é importante entender que a cobertura do código ajuda a entender a qualidade dos procedimentos de teste, não a qualidade do código em si .

Isso parece ser bastante relevante aqui. Se você tem um conjunto de casos de teste que consegue atingir um certo nível de cobertura (código ou não), é bem provável que esteja invocando o código em teste com um conjunto bastante exaustivo de valores de entrada! Isso não informa muito sobre o código em teste (a menos que o código exploda ou gere falhas detectáveis), mas oferece confiança no conjunto de casos de teste .

Em uma interessante mudança de visão do Necker Cube , o código de teste agora está sendo testado pelo código em teste!

David Tonhofer
fonte
-3

Existem várias maneiras de garantir que um programa faça o que você pretende e de garantir que as modificações não produzam efeitos indesejados.

Teste é um. Evitar a mutação de dados é outro. Então é um sistema de tipos. Ou verificação formal.

Portanto, embora eu concorde que o teste geralmente é uma coisa boa, uma determinada porcentagem do teste pode não significar muito. Prefiro confiar em algo escrito em Haskell sem testes do que em uma biblioteca PHP bem testada

Andrea
fonte
Essa é apenas a sua opinião ou você pode apoiá-la de alguma forma?
mosquito
2
Testar não é uma maneira de garantir que um programa faça o que você pretende.
Andrés F.
1
Então eu fiquei a me perguntar o teste é
Andrea
@gnat esta é obviamente a minha opinião. Ainda assim, diz o que diz. Tomei Haskell como um exemplo de linguagem cujo compilador é muito rigoroso e oferece muitas garantias sobre a boa formação da entrada, os tipos, os efeitos colaterais, a mutação dos dados. Tomei o PHP como exemplo de uma linguagem cujo intérprete é muito branda e que nem sequer tem uma especificação. Mesmo na ausência de testes, a presença de todas as garantias do sistema de tipos e efeitos geralmente produz um grau decente de confiabilidade. Para compensar isso com testes, seria preciso ter um conjunto muito abrangente
Andrea
Talvez eu estivesse um pouco apressado quando escrevi - estava ao telefone - mas ainda acho que há razão. Eu não quero bash em PHP, mas eu acho que dizer que, em comparação Haskell dá um grau maior tanto de confiabilidade é uma indicação objetiva
Andrea