Teste: determinístico ou não determinístico?

17

É melhor ter um

  • Conjunto de testes determinísticos, que resulta nos mesmos testes
  • Conjunto de testes não determinísticos, que possivelmente cobre mais casos

?

Exemplo: você escreve um conjunto de testes para testar a funcionalidade do controlador em um aplicativo MVC. O controlador requer dados do aplicativo de um banco de dados como entrada durante o teste. Existem duas opções para fazer isso:

  • Você codifica quais linhas do banco de dados de teste são selecionadas como entrada (por exemplo, as linhas 10 e 412)
  • Você usa um gerador de números aleatórios para escolher aleatoriamente os dados do banco de dados (duas linhas selecionadas por um gerador de números aleatórios)

A primeira é determinística: toda execução do teste para a mesma revisão de código deve produzir o mesmo resultado. A segunda é não determinística: toda execução do conjunto de testes tem a possibilidade de gerar um resultado diferente. Os dados escolhidos aleatoriamente podem, no entanto, ser uma melhor representação dos casos extremos de dados. Pode simular um usuário alimentando nossos controladores com dados imprevisíveis melhor?

Quais são os motivos para escolher um sobre o outro?

DCKing
fonte
5
Esse teste simplesmente falha, às vezes. martinfowler.com/articles/nonDeterminism.html
Obrigado por esse link. Com esse artigo em mente, senti que precisava esclarecer que não determinismo significa no contexto desse conjunto de testes. Como os dados são selecionados aleatoriamente em um banco de dados, todos os dados fornecidos ao controlador são dados válidos por padrão. Isso significa que os falsos negativos não existem no conjunto de testes quando se trata do não determinismo. De certa forma, essa aleatoriedade simula um usuário selecionando dados 'aleatoriamente' para uso em um controlador. Este não é necessariamente o mesmo não determinismo que o artigo discute, certo?
DCKing
10
@DCKing: Considere o que acontece se o teste falhar. Ok, você tem um bug. E agora? Execute-o novamente no modo de depuração! Onde consegue! Assim como ocorre nas próximas cem vezes que você o executa, e depois escreve o problema como um ataque de raios cósmicos. Não determinístico nos testes parece absolutamente impraticável. Se você sentir a necessidade de cobrir mais terreno em seus casos de teste, cubra mais terreno. Inicie seu RNG com uma semente definida e execute o "teste" algumas centenas de vezes com valores consistentemente aleatórios.
Phoshi
1
(finalmente em torno de uma máquina onde eu poderia procurar corretamente Twitter - o " Aquele teste apenas falha, por vezes, " é a partir dos #FiveWordTechHorrors no Twitter - queria adequadamente creditá-lo)

Respostas:

31

Quando todas as execuções do conjunto de testes oferecem a possibilidade de produzir um resultado diferente, o teste é quase completamente inútil - quando o conjunto mostra um erro, você tem uma grande chance de não conseguir reproduzi-lo e quando tenta corrigir o erro. bug, você não pode verificar se sua correção funciona.

Portanto, quando achar que precisa usar algum tipo de gerador de números aleatórios para gerar seus dados de teste, sempre inicialize o gerador com a mesma semente ou persista seus dados de teste aleatórios em um arquivo antes de alimentá-lo em seu teste, para que você possa executar novamente o teste novamente com exatamente os mesmos dados da execução anterior. Dessa forma, você pode transformar qualquer teste não determinístico em um determinístico.

EDIT: Usar um gerador de números aleatórios para escolher alguns dados de teste é IMHO, às vezes, um sinal de preguiça de escolher boas dados de teste. Em vez de lançar 100.000 valores de teste escolhidos aleatoriamente e esperar que isso seja suficiente para descobrir todos os erros sérios por acaso, use melhor seu cérebro, escolha 10 a 20 casos "interessantes" e use-os para o conjunto de testes. Isso resultará não apenas em uma melhor qualidade de seus testes, mas também em um desempenho muito maior do conjunto.

Doc Brown
fonte
Obrigado pela sua resposta. Qual a sua opinião sobre o comentário que fiz à minha pergunta?
DCKing
1
@DCKing: se você realmente acha que um gerador aleatório será melhor para escolher bons casos de teste do que você (o que eu duvido), use-o uma vez para encontrar combinações de dados de teste onde seu programa falhar e coloque essas combinações na parte "codificada" do seu conjunto de testes.
Doc Brown
Obrigado novamente. Atualizei minha resposta para que não pareça se aplicar apenas aos aplicativos MVC.
DCKing
1
Em alguns contextos da interface do usuário (por exemplo, jogos com entrada do controlador) com programas de teste que geram entrada aleatória de chave podem ser úteis para testes de estresse. Eles podem descobrir defeitos difíceis de encontrar com informações deliberadas.
Gort the Robot
@StevenBurnap: bem, do jeito que eu entendo a pergunta, acho que o OP tinha em mente testes de regressão mais convencionais. Obviamente, concordo que o teste de estresse é um caso especial que também pode depender do hardware e resultar em comportamento não determinístico, mesmo quando você não usa um gerador aleatório. Isso é algo descrito no artigo vinculado por MichaelT no primeiro comentário abaixo da pergunta. E mesmo em testes de estresse com entrada aleatória, pode-se pelo menos tentar tornar o comportamento mais determinístico usando uma semente aleatória definida.
Doc Brown
4

Tanto determinístico quanto não determinístico têm um lugar

Eu os dividiria da seguinte maneira:

Testes unitários.

Eles devem ter testes determinísticos e repetíveis com os mesmos dados exatos todas as vezes. Os testes de unidade acompanham seções de código específicas e isoladas e devem testá-las de maneira determinística.

Testes de estresse funcionais e de entrada.

Eles podem usar a abordagem não determinística com as seguintes advertências:

  • esse fato é claramente delineado e chamado
  • os valores aleatórios selecionados são registrados e podem ser tentados novamente manualmente
Michael Durrant
fonte
3

Ambos.

Testes determinísticos e não determinísticos têm diferentes casos de uso e valores diferentes para o seu conjunto. Geralmente não determinístico não pode fornecer a mesma precisão que o teste determinístico, que lentamente se transformou em "teste não determinístico não fornece valor". Isto é falso. Eles podem ser menos precisos, mas também podem ser muito mais amplos, o que tem seus próprios benefícios.

Vamos dar um exemplo: você escreve uma função que classifica uma lista de números inteiros. Quais seriam alguns dos testes unitários determinísticos que você consideraria úteis?

  • Uma lista vazia
  • Uma lista com apenas um elemento
  • Uma lista com o mesmo elemento
  • Uma lista com vários elementos exclusivos
  • Uma lista com vários elementos, alguns dos quais são duplicados
  • Uma lista com NaN, INT_MINeINT_MAX
  • Uma lista que já está parcialmente classificada
  • Uma lista com 10.000.000 de elementos

E isso é apenas uma função de classificação! Claro, você poderia argumentar que alguns deles são desnecessários ou que alguns deles podem ser descartados com raciocínio informal. Mas somos engenheiros e vimos o raciocínio informal explodir em nossa cara. Sabemos que não somos inteligentes o suficiente para entender completamente os sistemas que construímos ou manter totalmente a complexidade em nossas cabeças. É por isso que escrevemos testes em primeiro lugar. A adição de testes não determinísticos apenas diz que podemos não ser necessariamente inteligentes o suficiente para conhecer todos os bons testes a priori. Ao lançar dados semi-aleatórios em sua função, é muito mais provável que você encontre um caso extraviado que perdeu.

Obviamente, isso também não exclui os testes determinísticos. Testes não determinísticos ajudam a encontrar bugs em grandes áreas do programa. Depois de encontrar os bugs, no entanto, você precisa de uma maneira reproduzível para mostrar que foi corrigido. Então:

  • Use testes não determinísticos para encontrar erros no seu código.
  • Use testes determinísticos para verificar as correções no seu código.

Observe que isso significa que muitos conselhos sólidos sobre testes de unidade não se aplicam necessariamente a testes não determinísticos. Por exemplo, eles devem ser rápidos. Os testes de propriedades de baixo nível devem ser rápidos, mas um teste não determinístico como "simular um usuário clicando aleatoriamente nos botões do seu site e garantir que você nunca receba um erro de 500" deve favorecer a compreensão sobre a velocidade. Basta fazer um teste como esse, independentemente do seu processo de compilação, para não atrapalhar o desenvolvimento. Por exemplo, execute-o em sua própria caixa de teste particular.

Hovercouch
fonte
-1

Você realmente não quer determinístico versus não determinístico.

O que você pode querer é "sempre o mesmo" vs. "nem sempre o mesmo".

Por exemplo, você pode ter um número de compilação que aumenta a cada compilação e quando deseja alguns números aleatórios, inicializa um gerador de números aleatórios com o número de compilação como semente. Portanto, a cada compilação, você faz seus testes com valores diferentes, oferecendo mais chances de encontrar bugs.

Mas quando um bug é encontrado, tudo o que você precisa fazer é executar o teste com o mesmo número de compilação e é reproduzível.

gnasher729
fonte
1
Ou, se você não tiver um número de compilação para usar, coloque o valor inicial da semente na saída da execução de teste, para poder executar novamente os testes com a mesma semente.
RemcoGerlich 12/12