Os testes de unidade de gravação manual são à prova de exemplo?


9

Sabemos que escrever testes JUnit demonstra um caminho específico através do seu código.

Um dos meus associados comentou:

A gravação manual de testes de unidade é a Prova por exemplo .

Ele vinha do fundo de Haskell, que possui ferramentas como o Quickcheck e a capacidade de raciocinar sobre o comportamento do programa com tipos .

Sua implicação era que existem muitas outras combinações de entradas que não são experimentadas por esse método para as quais seu código não foi testado.

Minha pergunta é: Os testes de unidade de gravação manual são prova por exemplo?


3
Não, não escrevendo / usando testes. Alegar que seus testes de unidade são a prova de que não há nada errado com o programa é a Prova por Exemplo (uma generalização inadequada). Os testes não são sobre provar matematicamente a correção do código - os testes são, por natureza, verificações experimentais. É uma rede de segurança que ajuda a criar confiança, dizendo algo sobre o código. Mas você é quem precisa escolher uma boa estratégia para sondar o código, e você é quem precisa interpretar o que esses dados significam.
Filip Milovanović

Respostas:


10

Se você escolher aleatoriamente entradas para teste, suponho que seja possível que você esteja exercendo uma falácia lógica de Prova por Exemplo.

Mas bons testes de unidade nunca fazem isso. Em vez disso, eles lidam com intervalos e casos extremos.

Por exemplo, se você escrevesse testes de unidade para uma função de valor absoluto que aceita um número inteiro como entrada, não seria necessário testar todos os valores possíveis de entrada para provar que o código funciona. Para obter um teste abrangente, você precisaria apenas de cinco valores: -1, 0, 1 e os valores máximo e mínimo para o número inteiro de entrada.

Esses cinco valores testam todos os intervalos e bordas possíveis da função. Você não precisa testar todos os outros valores de entrada possíveis (ou seja, todos os números que o tipo inteiro possa representar) para obter um alto nível de confiança de que a função funciona para todos os valores de entrada.


11
Um testador de código entra em um bar e pede uma cerveja. 5 cervejas. -1 cervejas, MAX_VALUE cervejas, uma galinha. um nulo.
Neil

2
Os "5 valores" são pura bobagem. Considere uma função trivial como int foo(int x) { return 1234/(x - 100); }. Observe também que (dependendo do que você está testando), pode ser necessário garantir que a entrada inválida ("fora do intervalo") retorne resultados corretos (por exemplo, que `` find_thing (thing) `retorne corretamente algum tipo de status" não encontrado " se a coisa não foi encontrada).
Brendan

3
@Brendan: Não há nada de significativo em ser cinco valores; por acaso são cinco valores no meu exemplo. Seu exemplo tem um número diferente de testes porque você está testando uma função diferente. Não estou dizendo que toda função requer exatamente cinco testes; você deduziu isso lendo a minha resposta.
Robert Harvey

11
As bibliotecas de teste generativas geralmente são melhores para testar casos extremos do que você. Se, por exemplo, você estava usando bóias em vez de números inteiros, a biblioteca também iria verificar -Inf, Inf, NaN, 1e-100, -1e-100, -0, 2e200... Eu prefiro não tem que fazer todos aqueles manualmente.
Hovercouch

@ Hovercouch: Se você souber de uma boa, eu adoraria ouvir sobre isso. O melhor que eu vi foi Pex; era incrivelmente instável, no entanto. Lembre-se, estamos falando de funções relativamente simples aqui. As coisas ficam mais difíceis quando você lida com coisas como lógica de negócios da vida real.
Robert Harvey

8

Qualquer teste de software é como "Prova por exemplo", não apenas testes de unidade usando uma ferramenta como JUnit. E isso não é nova sabedoria, há uma citação de Dijkstra de 1960, que diz essencialmente o mesmo:

"O teste mostra a presença, não a ausência de bugs"

(basta substituir as palavras "shows" por "provas"). No entanto, isso também é verdade para ferramentas que geram dados de teste aleatórios. O número de entradas possíveis para uma função do mundo real é geralmente maior em ordens de magnitudes do que o número de casos de teste que se pode produzir e verificar em relação a um resultado esperado dentro da idade do universo, independentemente do método de geração desses casos. mesmo se alguém usar uma ferramenta geradora para produzir muitos dados de teste, não há garantia de não perder o caso de teste que poderia ter detectado um determinado bug.

Às vezes, testes aleatórios podem revelar um bug que foi ignorado pelos casos de teste criados manualmente. Mas, em geral, é mais eficiente criar testes cuidadosamente para a função a ser testada e garantir que um usuário obtenha uma cobertura completa de código e ramificação com o menor número possível de casos de teste. Às vezes, é uma estratégia viável combinar testes gerados manualmente e aleatoriamente. Além disso, ao usar testes aleatórios, é preciso ter o cuidado de obter os resultados de maneira reproduzível.

Portanto, os testes criados manualmente não são de forma alguma pior que os testes gerados aleatoriamente, geralmente o contrário.


11
Qualquer suíte de teste prático usando verificação aleatória também terá testes de unidade. (Tecnicamente, os testes de unidade são apenas um caso degenerado de teste aleatório.) Sua redação sugere que testes aleatórios são difíceis de serem realizados ou que a combinação de testes aleatórios e testes de unidade é difícil. Normalmente não é esse o caso. Na minha opinião, um dos maiores benefícios do teste aleatório é que ele incentiva fortemente a gravação de testes como propriedades sobre o código que sempre se espera. Eu preferiria ter essas propriedades explicitamente declaradas (e verificadas!) Do que inferir a elas alguns testes pontuais.
Derek Elkins saiu de SE

@ DerekElkins: "difícil" é IMHO o termo errado. Os testes aleatórios precisam de bastante esforço, e esse é o esforço que reduz o tempo disponível para testes de artesanato (e se você tem pessoas seguindo apenas slogans como o mencionado na pergunta, eles provavelmente não farão nenhum artesanato). Apenas lançar muitos dados de teste aleatórios em um pedaço de código é apenas metade do trabalho, é preciso também produzir os resultados esperados para cada uma dessas entradas de teste. Para alguns cenários, isso pode ser feito automaticamente. Para outros, não.
Doc Brown

Embora definitivamente existam momentos em que é necessário pensar em uma boa distribuição, isso geralmente não é um problema importante. Seu comentário sugere que você está pensando sobre isso da maneira errada. As propriedades que você escreve para verificação aleatória são as mesmas que você escreveria para verificação de modelo ou para provas formais. Na verdade, eles podem ser e foram usados ​​para todas essas coisas ao mesmo tempo. Não há "resultados esperados" que você precise produzir também. Em vez disso, basta declarar uma propriedade que deve sempre manter. Alguns exemplos: 1) colocando algo em uma pilha e ...
Derek Elkins saiu de SE

... então estalar deve ser o mesmo que não fazer nada; 2) qualquer cliente com saldo superior a US $ 10.000 deve obter a alta taxa de juros do saldo e somente então; 3) a posição do sprite está sempre dentro da caixa delimitadora da tela. Algumas propriedades podem muito bem corresponder a testes de pontos, por exemplo, "quando o saldo for $ 0, dê o aviso de saldo zero". As propriedades são especificações parciais com o ideal de obter uma especificação total. Ter dificuldade em pensar nessas propriedades significa que você não sabe ao certo qual é a especificação e geralmente significa que teria dificuldade em pensar em bons testes de unidade.
Derek Elkins saiu de SE

0

Escrever testes manualmente é "prova por exemplo". Mas o QuickCheck também é assim e, em certa medida, os sistemas de tipos. Qualquer coisa que não seja uma verificação formal direta será limitada no que diz sobre seu código. Em vez disso, você deve pensar em termos do mérito relativo das abordagens.

Testes generativos, como o QuickCheck, são realmente bons para varrer um amplo espaço de entradas. Também é muito melhor para lidar com casos extremos do que com testes manuais: as bibliotecas de testes generativas terão mais experiência com isso do que você. Por outro lado, eles apenas falam sobre invariantes, não sobre resultados específicos. Portanto, para validar que seu programa está obtendo os resultados corretos, você ainda precisa de alguns testes manuais para verificar isso foo(bar) = baz.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.