Pergunta

Tenho um colega de trabalho que escreve testes unitários para objetos que preenchem seus campos com dados aleatórios.O motivo é que ele oferece uma gama mais ampla de testes, já que testará muitos valores diferentes, enquanto um teste normal usa apenas um único valor estático.

Eu dei a ele uma série de razões diferentes contra isso, sendo as principais:

  • valores aleatórios significam que o teste não é realmente repetível (o que também significa que se o teste falhar aleatoriamente, ele poderá falhar no servidor de compilação e interromper a compilação)
  • se for um valor aleatório e o teste falhar, precisamos a) consertar o objeto eb) nos forçar a testar esse valor todas as vezes, para sabermos que funciona, mas como é aleatório, não sabemos qual era o valor

Outro colega de trabalho acrescentou:

  • Se eu estiver testando uma exceção, valores aleatórios não garantirão que o teste termine no estado esperado
  • dados aleatórios são usados ​​para limpar um sistema e testes de carga, não para testes de unidade

Alguém mais pode acrescentar razões adicionais que eu possa dar para que ele pare de fazer isso?

(Ou, alternativamente, este é um método aceitável de escrever testes unitários, e eu e meu outro colega de trabalho estamos errados?)

Foi útil?

Solução

Há um compromisso.Seu colega de trabalho está realmente no caminho certo, mas acho que ele está fazendo errado.Não tenho certeza se testes totalmente aleatórios são muito úteis, mas certamente não são inválidos.

Uma especificação de programa (ou unidade) é uma hipótese de que existe algum programa que a atenda.O próprio programa é então uma prova dessa hipótese.O que o teste unitário deveria ser é uma tentativa de fornecer contra-evidências para refutar que o programa funciona de acordo com as especificações.

Agora, você pode escrever os testes unitários manualmente, mas é realmente uma tarefa mecânica.Pode ser automatizado.Tudo que você precisa fazer é escrever a especificação, e uma máquina pode gerar muitos testes de unidade que tentam quebrar seu código.

Não sei qual idioma você está usando, mas veja aqui:

Javahttp://funcionaljava.org/

Scala (ou Java)http://github.com/rickynils/scalacheck

Haskellhttp://www.cs.chalmers.se/~rjmh/QuickCheck/

.LÍQUIDO:http://blogs.msdn.com/dsyme/archive/2008/08/09/fscheck-0-2.aspx

Essas ferramentas pegarão suas especificações bem formadas como entrada e gerarão automaticamente quantos testes de unidade você desejar, com dados gerados automaticamente.Eles usam estratégias de "redução" (que você pode ajustar) para encontrar o caso de teste mais simples possível para quebrar seu código e para garantir que ele cubra bem os casos extremos.

Bons testes!

Outras dicas

Esse tipo de teste é chamado de Teste de macaco.Quando bem feito, pode eliminar insetos dos cantos realmente escuros.

Para resolver suas preocupações sobre a reprodutibilidade:a maneira correta de abordar isso é registrar as entradas de teste com falha, gerar um teste de unidade, que investiga o família inteira do bug específico;e inclua no teste de unidade a entrada específica (dos dados aleatórios) que causou a falha inicial.

Há um meio-termo aqui que tem alguma utilidade, que é semear seu PRNG com uma constante.Isso permite gerar dados 'aleatórios' que podem ser repetidos.

Pessoalmente, acho que há lugares onde dados aleatórios (constantes) são úteis nos testes - depois que você pensa que fez todos os cantos cuidadosamente pensados, o uso de estímulos de um PRNG às vezes pode encontrar outras coisas.

No livro Código bonito, há um capítulo chamado "Belos Testes", onde ele passa por uma estratégia de testes para o Pesquisa binária algoritmo.Um parágrafo é chamado de "Atos aleatórios de teste", no qual ele cria matrizes aleatórias para testar minuciosamente o algoritmo.Você pode ler um pouco disso on-line no Google Livros, página 95, mas é um ótimo livro que vale a pena ter.

Basicamente, isso apenas mostra que gerar dados aleatórios para teste é uma opção viável.

Uma vantagem para quem analisa os testes é que dados arbitrários claramente não são importantes.Já vi muitos testes que envolveram dezenas de dados e pode ser difícil dizer o que precisa ser assim e o que acontece ser assim.Por exemplo.Se um método de validação de endereço for testado com um CEP específico e todos os outros dados forem aleatórios, você pode ter certeza de que o CEP é a única parte importante.

Se você estiver fazendo TDD, eu diria que dados aleatórios são uma abordagem excelente.Se o seu teste for escrito com constantes, você só poderá garantir que seu código funcione para o valor específico.Se o seu teste falhar aleatoriamente no servidor de compilação, provavelmente há um problema na forma como o teste foi escrito.

Dados aleatórios ajudarão a garantir que qualquer refatoração futura não dependa de uma constante mágica.Afinal, se seus testes são sua documentação, então a presença de constantes não implica que ele só precisa funcionar para essas constantes?

Estou exagerando, porém prefiro injetar dados aleatórios em meu teste como um sinal de que "o valor desta variável não deve afetar o resultado deste teste".

Direi, porém, que se você usar uma variável aleatória e então bifurcar seu teste com base nessa variável, isso é um cheiro.

  • se for um valor aleatório e o teste falhar, precisamos a) consertar o objeto eb) nos forçar a testar esse valor todas as vezes, para sabermos que funciona, mas como é aleatório, não sabemos qual era o valor

Se o seu caso de teste não registrar com precisão o que está testando, talvez seja necessário recodificar o caso de teste.Sempre quero ter logs aos quais possa consultar casos de teste, para saber exatamente o que causou a falha, seja usando dados estáticos ou aleatórios.

Seu colega de trabalho está fazendo teste de fuzz, embora ele não saiba disso.Eles são especialmente valiosos em sistemas de servidores.

Sou a favor de testes aleatórios e os escrevo.No entanto, se eles são apropriados em um ambiente de construção específico e em quais conjuntos de testes devem ser incluídos é uma questão mais sutil.

Testes aleatórios executados localmente (por exemplo, durante a noite em sua caixa de desenvolvimento) encontraram bugs óbvios e obscuros.Os obscuros são misteriosos o suficiente para que eu pense que testes aleatórios foram realmente os únicos realistas para eliminá-los.Como teste, peguei um bug difícil de encontrar, descoberto por meio de testes aleatórios, e pedi a meia dúzia de desenvolvedores de crack para revisar a função (cerca de uma dúzia de linhas de código) onde ele ocorreu.Ninguém foi capaz de detectá-lo.

Muitos de seus argumentos contra dados aleatórios são do tipo "o teste não é reproduzível".No entanto, um teste randomizado bem escrito irá capturar a semente usada para iniciar a semente aleatória e produzi-la em caso de falha.Além de permitir que você repita o teste manualmente, isso permite criar trivialmente novos testes que testam o problema específico codificando a semente para esse teste.Claro, provavelmente é melhor codificar manualmente um teste explícito que cubra esse caso, mas a preguiça tem suas virtudes e isso permite até mesmo gerar automaticamente novos casos de teste a partir de uma semente com falha.

O único ponto que você destacou e que não posso debater, entretanto, é que isso quebra os sistemas de construção.A maioria dos testes de construção e integração contínua espera que os testes façam sempre a mesma coisa.Portanto, um teste que falha aleatoriamente criará o caos, falhando aleatoriamente e apontando o dedo para mudanças que eram inofensivas.

Uma solução, então, é ainda executar seus testes aleatórios como parte dos testes de construção e CI, mas execute-o com uma semente fixa, para um número fixo de iterações.Portanto, o teste sempre faz a mesma coisa, mas ainda explora grande parte do espaço de entrada (se você executá-lo em várias iterações).

Localmente, por exemplo, ao alterar a classe em questão, você pode executá-la para mais iterações ou com outras sementes.Se os testes randomizados se tornarem mais populares, você poderia até imaginar um conjunto específico de testes que são conhecidos como aleatórios, que poderiam ser executados com sementes diferentes (portanto, com cobertura crescente ao longo do tempo) e onde as falhas não significariam a mesma coisa. como sistemas de CI determinísticos (ou seja, as execuções não estão associadas 1:1 às alterações de código e, portanto, você não aponta o dedo para uma alteração específica quando as coisas falham).

Há muito a ser dito sobre testes randomizados, especialmente aqueles bem escritos, então não se precipite em descartá-los!

Você pode gerar alguns dados aleatórios uma vez (quero dizer, exatamente uma vez, não uma vez por execução de teste) e usá-los em todos os testes posteriores?

Definitivamente, posso ver o valor da criação de dados aleatórios para testar os casos nos quais você não pensou, mas você está certo, ter testes de unidade que podem passar ou falhar aleatoriamente é uma coisa ruim.

Você deve se perguntar qual é o objetivo do seu teste.
Testes unitários tratam da verificação de lógica, fluxo de código e interações de objetos.O uso de valores aleatórios tenta atingir um objetivo diferente, reduzindo assim o foco e a simplicidade do teste.É aceitável por motivos de legibilidade (geração de UUID, ids, chaves, etc.).
Especificamente para testes de unidade, não consigo me lembrar de uma vez que esse método tenha encontrado problemas com sucesso.Mas tenho visto muitos problemas de determinismo (nos testes) tentando ser inteligente com valores aleatórios e principalmente com datas aleatórias.
O teste Fuzz é uma abordagem válida para testes de integração e testes de ponta a ponta.

Se você estiver usando entradas aleatórias para seus testes, precisará registrar as entradas para poder ver quais são os valores.Dessa forma, se você encontrar algum caso extremo, você pode escreva o teste para reproduzi-lo.Já ouvi os mesmos motivos de pessoas para não usar entradas aleatórias, mas depois que você tiver uma visão dos valores reais usados ​​para uma execução de teste específica, isso não será um problema tão grande.

A noção de dados “arbitrários” também é muito útil como forma de significar algo que é não importante.Temos alguns testes de aceitação que vêm à mente quando há muitos dados de ruído que não são relevantes para o teste em questão.

Dependendo do seu objeto/aplicativo, dados aleatórios teriam lugar no teste de carga.Acho que mais importante seria usar dados que testassem explicitamente as condições de contorno dos dados.

Acabamos de encontrar isso hoje.Eu queria pseudo-aleatório (assim pareceriam dados de áudio compactados em termos de tamanho).Eu TODO que eu também queria determinístico.rand() era diferente no OSX e no Linux.E a menos que eu replantasse, isso poderia mudar a qualquer momento.Então, mudamos para ser determinístico, mas ainda psuedo-aleatório:o teste é repetível, tanto quanto usar dados predefinidos (mas escritos de forma mais conveniente).

Isso foi NÃO testando por alguma força bruta aleatória através de caminhos de código.Essa é a diferença:ainda determinístico, ainda repetível, ainda usando dados que parecem entradas reais para executar um conjunto de verificações interessantes em casos extremos em lógica complexa.Ainda testes de unidade.

Isso ainda se qualifica como aleatório?Vamos conversar tomando cerveja.:-)

Posso imaginar três soluções para o problema dos dados de teste:

  • Teste com dados fixos
  • Teste com dados aleatórios
  • Gerar dados aleatórios uma vez, e use-os como seus dados fixos

Eu recomendaria fazer tudo o que precede.Ou seja, escreva testes de unidade repetíveis com alguns casos extremos resolvidos usando seu cérebro e alguns dados aleatórios que você gera apenas uma vez.Em seguida, escreva um conjunto de testes aleatórios que você executa também.

Nunca se deve esperar que os testes randomizados detectem algo que seus testes repetíveis não percebem.Você deve tentar cobrir tudo com testes repetíveis e considerar os testes randomizados um bônus.Se eles encontrarem algo, deve ser algo que você não poderia ter previsto razoavelmente;um verdadeiro excêntrico.

Como o seu cara pode executar o teste novamente quando não conseguiu ver se ele corrigiu o problema?Ou sejaele perde a repetibilidade dos testes.

Embora eu ache que provavelmente há algum valor em lançar uma carga de dados aleatórios nos testes, como mencionado em outras respostas, isso se enquadra mais no título de teste de carga do que qualquer outra coisa.É praticamente uma prática de “teste pela esperança”.Acho que, na realidade, seu cara simplesmente não está pensando no que está tentando testar e compensando essa falta de reflexão esperando que a aleatoriedade acabe capturando algum erro misterioso.

Portanto, o argumento que usaria com ele é que ele está sendo preguiçoso.Ou, dito de outra forma, se ele não dedicar tempo para entender o que está tentando testar, provavelmente mostra que ele realmente não entende o código que está escrevendo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top