Dados aleatórios em testes de unidade?

136

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

Eu dei a ele várias razões diferentes contra isso, sendo as principais:

  • valores aleatórios significa que o teste não é realmente repetível (o que também significa que, se o teste falhar aleatoriamente, poderá ocorrer 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) forçar-nos a testar sempre esse valor, portanto sabemos que funciona, mas como é aleatório, não sabemos qual era o valor

Outro colega acrescentou:

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

Alguém pode acrescentar outros motivos que eu posso dar para ele parar de fazer isso?

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

Adam V
fonte
32
"valores aleatórios significa que o teste não é verdadeiramente repetível" não é verdadeiro, pois os tets usarão números pseudo-aleatórios. Forneça a mesma semente inicial, obtenha a mesma sequência de testes "aleatórios".
Raedwald 8/09/11
11
Anedota: Certa vez, escrevi uma classe de exportação para CSV, e os testes aleatórios revelaram um erro quando os caracteres de controle foram colocados no final de uma célula. Sem testes aleatórios, eu nunca pensaria em adicionar isso como um caso de teste. Sempre falhava? Não. É um teste perfeito? Não. Isso me ajudou a detectar e corrigir um bug? Sim.
Tyzoid
1
Os testes também podem servir como documentação, para explicar quando o código espera como entrada e o que é esperado como saída. Ter um teste com dados arbitrários claros pode ser mais simples e mais explicativo do que o código que gera dados aleatórios.
splintor
Se o seu teste de unidade falhar devido a um valor gerado aleatoriamente, e esse valor não fizer parte de uma afirmação, boa sorte com a depuração do teste de unidade.
eriksmith200

Respostas:

72

Há um compromisso. Seu colega de trabalho gosta de algo, mas acho que ele está fazendo errado. Não tenho certeza de que o teste totalmente aleatório seja muito útil, mas certamente não é inválido.

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

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

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

Java http://functionaljava.org/

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

Haskell http://www.cs.chalmers.se/~rjmh/QuickCheck/

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

Essas ferramentas receberã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 "encolhimento" (que você pode ajustar) para encontrar o caso de teste mais simples possível para quebrar seu código e garantir que ele cubra bem os casos extremos.

Teste feliz!

Apocalisp
fonte
1
+1 a isso. O ScalaCheck faz um trabalho fenomenal de gerar dados de teste aleatórios e minimizados de forma repetível.
Daniel Spiewak 18/09/08
17
Não é aleatório. É arbitrário. Grande diferença :)
Apocalisp
O reductiotest.org agora parece mais existir, e o Google não me indicou nenhum outro lugar. Alguma idéia de onde está agora?
Raedwald 8/09/11
Agora faz parte da biblioteca Java Funcional. Link editado. Mas eu usaria o Scalacheck para testar o código Java.
Apocalisp
O ScalaCheck mudou-se para o GitHub. Link atualizado na resposta. Também é útil para Java, não apenas para o Scala. (O link antigo era code.google.com/p/scalacheck )
RobertB
38

Esse tipo de teste é chamado de teste de macaco . Quando bem feito, ele pode soltar bugs dos cantos realmente escuros.

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

Dragão Prateado
fonte
26

Há uma casa de recuperação aqui que tem alguma utilidade, que é semear seu PRNG com uma constante. Isso permite gerar dados 'aleatórios' repetíveis.

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

Will Dean
fonte
4
Eu já vi esse trabalho bem em um sistema que tinha muitos bloqueios e threads. A semente 'aleatória' era gravada em um arquivo em cada execução; se uma execução falhasse, poderíamos descobrir o caminho que o código seguia e escrever um teste de unidade escrito à mão para o caso que perdemos.
Ian Ringrose
PRNG de quê?
systemovich
Pseudo Random Number Generator
Will Dean
16

No livro Beautiful Code , há um capítulo chamado "Beautiful Tests", onde ele passa por uma estratégia de teste para o algoritmo de busca binária . 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 online no Google Livros, página 95 , mas é um ótimo livro que vale a pena ter.

Então, basicamente, isso apenas mostra que a geração de dados aleatórios para teste é uma opção viável.

mreggen
fonte
16

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

Execute localmente (por exemplo, durante a noite em sua caixa de desenvolvimento) testes aleatórios descobriram bugs óbvios e obscuros. Os obscuros são misteriosos o suficiente para que eu acho que o teste aleatório foi realmente o único realista a liberá-los. Como teste, peguei um bug difícil de encontrar descoberto por meio de testes aleatórios e tive uma meia dúzia de desenvolvedores de crack revisando a função (cerca de uma dúzia de linhas de código) onde ocorreu. Nenhum foi capaz de detectá-lo.

Muitos de seus argumentos contra dados aleatórios são sabores de "o teste não é reproduzível". No entanto, um teste aleatório bem escrito capturará a semente usada para iniciar a semente aleatória e a produzirá em falha. Além de permitir que você repita o teste manualmente, isso permite que você crie trivialmente um novo teste que testa o problema específico codificando a semente desse teste. É claro que é provavelmente melhor codificar manualmente um teste explícito que cubra esse caso, mas a preguiça tem suas virtudes, e isso ainda permite que você gere essencialmente automaticamente novos casos de teste a partir de uma semente que falhou.

O único argumento que você enfatiza que não posso debater é que isso quebra os sistemas de construção. A maioria dos testes de compilação e integração contínua espera que os testes façam o mesmo sempre. Portanto, um teste que falha aleatoriamente cria caos, falha aleatória e aponta os dedos para mudanças inofensivas.

Uma solução, então, é ainda executar seus testes aleatórios como parte dos testes de compilação e IC, mas executá-lo com uma semente fixa, para um número fixo de iterações . Portanto, o teste sempre faz a mesma coisa, mas ainda explora um monte de espaço de entrada (se você o executar para 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 o teste aleatório se tornar cada vez mais popular, você pode imaginar um conjunto específico de testes que são conhecidos por serem aleatórios, que podem ser executados com sementes diferentes (portanto, com cobertura crescente ao longo do tempo) e onde falhas não significariam a mesma coisa como sistemas de IC determinísticos (ou seja, execuções não são associadas 1: 1 a alterações de código e, portanto, você não aponta uma alteração específica quando as coisas falham).

Há muito o que dizer sobre os testes randomizados, especialmente os bem escritos, por isso não seja rápido demais para descartá-los!

BeeOnRope
fonte
14

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

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

Estou exagerando, no entanto, prefiro injetar dados aleatórios no meu teste como um sinal de que "o valor dessa variável não deve afetar o resultado desse teste".

No entanto, direi que se você usar uma variável aleatória, faça o teste com base nessa variável, então isso é um cheiro.

Jimmy Bosse
fonte
10

Uma vantagem para quem olha para os testes é que dados arbitrários claramente não são importantes. Eu já vi muitos testes que envolveram dezenas de dados e pode ser difícil dizer o que precisa ser assim e o que acontece dessa maneira. 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.

Trystan Spangler
fonte
9
  • se for um valor aleatório e o teste falhar, precisamos: a) consertar o objeto eb) forçar-nos a testar sempre esse valor, portanto sabemos 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á sendo testado, talvez seja necessário recodificá-lo. Eu sempre quero ter logs aos quais possa me referir para casos de teste, para saber exatamente o que causou a falha, usando dados estáticos ou aleatórios.

EBGreen
fonte
9

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

Robert Gould
fonte
2
mas isso não é fundamentalmente diferente dos testes de unidade? e feito em um momento diferente?
endolith
1
@endolith não há nenhuma lei da física forçá-lo a executar testes específicos em determinadas épocas
user253751
1
@immibis Mas existem boas razões para fazer testes específicos em momentos específicos. Você não executa uma bateria de testes de unidade sempre que um usuário clica em um botão "Ok".
endolith
5

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 na criação de dados aleatórios para testar os casos em que você não pensou, mas você está certo, ter testes de unidade que podem passar ou falhar aleatoriamente é uma coisa ruim.

Programador foragido
fonte
5

Você deve se perguntar qual é o objetivo do seu teste.
Os testes de unidade são sobre verificação de lógica, fluxo de código e interações com objetos. O uso de valores aleatórios tenta atingir um objetivo diferente, reduzindo o foco e a simplicidade do teste. É aceitável por motivos de legibilidade (gerando UUID, IDs, chaves, etc.).
Especificamente para testes de unidade, não me lembro nem mesmo quando esse método foi bem-sucedido ao encontrar problemas. Mas já vi muitos problemas de determinismo (nos testes) tentando ser espertos com valores aleatórios e principalmente com datas aleatórias.
O teste de fuzz é uma abordagem válida para testes de integração e testes de ponta a ponta .

Ohad Bruker
fonte
Eu acrescentaria que o uso de entrada aleatória para difusão é um substituto ruim para a difusão guiada por cobertura, quando possível.
gobenji 23/04
1

Se você estiver usando entrada aleatória para seus testes, precisará registrar as entradas para poder ver quais são os valores. Dessa forma, se houver algum caso extremo, você pode escrever o teste para reproduzi-lo. Ouvi as mesmas razões das pessoas por não usarem entradas aleatórias, mas depois de ter uma idéia dos valores reais usados ​​para uma determinada execução de teste, não há tanto problema.

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 onde há muitos dados de ruído que não são relevantes para o teste em questão.

craigb
fonte
0

Dependendo do seu objeto / aplicativo, os dados aleatórios teriam um lugar no teste de carga. Eu acho que o mais importante seria usar dados que testem explicitamente as condições de contorno dos dados.

EBGreen
fonte
0

Acabamos de encontrar isso hoje. Eu queria pseudo-aleatório (para parecer dados de áudio compactados em termos de tamanho). Eu também queria que fosse determinista . rand () era diferente no OSX e no Linux. E, a menos que eu tenha re-semeado, ele pode mudar a qualquer momento. Então, nós o mudamos para ser determinístico, mas ainda aleatório: o teste é repetível, tanto quanto usando dados enlatados (mas mais convenientemente escritos).

Isto NÃO estava sendo testado por alguma força bruta aleatória através de caminhos de código. Essa é a diferença: ainda determinística, 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 é aleatório? Vamos conversar sobre cerveja. :-)

Tim James
fonte
0

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

  • Teste com dados fixos
  • Teste com dados aleatórios
  • Gere dados aleatórios uma vez e use-os como dados fixos

Eu recomendaria fazer todas as opções acima . Ou seja, escreva testes de unidade repetíveis com alguns casos extremos elaborados 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 perdem. Você deve tentar cobrir tudo com testes repetíveis e considerar os testes aleatórios um bônus. Se eles encontrarem algo, deve ser algo que você não poderia ter previsto razoavelmente; um verdadeiro estranho.

Tom W
fonte
-2

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

Embora eu ache que provavelmente haja algum valor em arremessar uma carga de dados aleatórios nos testes, como mencionado em outras respostas, ele se encaixa mais no título de teste de carga do que qualquer outra coisa. É praticamente uma prática de "teste por esperança". Penso que, na realidade, seu sujeito simplesmente não está pensando no que está tentando testar, e compensar essa falta de pensamento, esperando que a aleatoriedade acabe por capturar algum erro misterioso.

Portanto, o argumento que eu usaria com ele é que ele está sendo preguiçoso. Ou, em outras palavras, se ele não tiver tempo para entender o que está tentando testar, provavelmente mostra que ele realmente não entende o código que está escrevendo.

Greg Whitfield
fonte
3
É possível registrar os dados aleatórios ou a semente aleatória para que o teste possa ser reproduzido.
Cbp 29/05/2009