Como devo testar a aleatoriedade?

127

Considere um método para embaralhar aleatoriamente elementos em uma matriz. Como você escreveria um teste de unidade simples, porém robusto, para garantir que isso funcione?

Eu vim com duas idéias, ambas com falhas visíveis:

  • Embaralhe a matriz e verifique se a ordem é diferente da anterior. Isso soa bem, mas falha se o embaralhar na mesma ordem. (Improvável, mas possível.)
  • Embaralhe a matriz com uma semente constante e verifique-a com a saída predeterminada. Isso depende da função aleatória sempre retornando os mesmos valores, dada a mesma semente. No entanto, isso às vezes é uma suposição inválida .

Considere uma segunda função que simula a rolagem de dados e retorna um número aleatório. Como você testaria essa função? Como você testaria se a função ...

  • nunca retorna um número fora dos limites especificados?
  • retorna números em uma distribuição válida? (Uniforme para um dado, normal para um grande número de dados.)

Estou procurando respostas que ofereçam informações sobre o teste não apenas desses exemplos, mas de elementos aleatórios do código em geral. Os testes de unidade são a solução certa aqui? Caso contrário, que tipo de testes são?


Só para facilitar a mente de todos, não estou escrevendo meu próprio gerador de números aleatórios.

dlras2
fonte
35
O acoplamento apertado mostra sua cabeça. Passe o objeto que gera os números aleatórios. Depois, durante o teste, você pode passar um objeto que gera um conjunto especificado de números para os quais você sabe como é o baralho após o shuffle. Você pode testar a aleatoriedade do seu gerador de números aleatórios separadamente.
Martin York
1
Eu consideraria fortemente o uso de uma rotina de biblioteca existente para shuffle (java Collections.shuffle () ou similar). Há uma história de advertência a ser lida em developer.com/tech/article.php/616221/… sobre como escrever um algoritmo de shuffle defeituoso. Para escrever uma função d6 (), seria testado o suficiente para ter certeza de que não geraria um número fora do intervalo e, em seguida, seria realizado um teste de chi quadrado na distribuição (o qui quadrado é bastante sensível a seqüências pseudo-aleatórias). Veja também o coeficiente de correlação serial.
"Isso depende da função aleatória sempre retornando os mesmos valores, dada a mesma semente. No entanto, isso às vezes é uma suposição inválida." Eu segui o link e não estou vendo a suposição inválida. Diz claramente: "Se a mesma semente é usada repetidamente, a mesma série de números é gerada".
Kyralessa
@Kyralessa "Não é garantido que a implementação do gerador de números aleatórios na classe Random permaneça a mesma nas principais versões do .NET Framework." Portanto, não é uma grande preocupação, mas ainda há algo a considerar.
Dlras2
4
@ Kyralessa Perdi a metade importante dessa citação: "Como resultado, o código do aplicativo não deve assumir que a mesma semente resultará na mesma sequência pseudo-aleatória em diferentes versões do .NET Framework".
Dlras2

Respostas:

102

Não acho que os testes de unidade sejam a ferramenta certa para testar a aleatoriedade. Um teste de unidade deve chamar um método e testar o valor retornado (ou estado do objeto) em relação a um valor esperado. O problema com o teste da aleatoriedade é que não há um valor esperado para a maioria das coisas que você deseja testar. Você pode testar com uma determinada semente, mas isso apenas testa a repetibilidade . Não oferece nenhuma maneira de medir o quão aleatória é a distribuição, ou se é mesmo aleatória.

Felizmente, existem muitos testes estatísticos que você pode executar, como a Bateria de Testes Aleatórios Diehard . Veja também:

  1. Como testar a unidade de um gerador de números pseudo-aleatórios?

    • Steve Jessop recomenda que você encontre uma implementação testada do mesmo algoritmo RNG que você está usando e compare sua saída com as sementes selecionadas em relação à sua própria implementação.
    • Greg Hewgill recomenda o conjunto ENT de testes estatísticos.
    • John D. Cook refere os leitores ao seu artigo do CodeProject Simple Random Number Generation , que inclui uma implementação do teste Kolmogorov-Smirnov mencionado no volume 2 de Donald Knuth, Algoritmos Seminuméricos.
    • Várias pessoas recomendam testar se a distribuição dos números gerados é uniforme, o teste Qui-quadrado e testar se a média e o desvio padrão estão dentro da faixa esperada. (Observe que testar a distribuição por si só não é suficiente. [1,2,3,4,5,6,7,8] é uma distribuição uniforme, mas certamente não é aleatória.)
  2. Teste de unidade com funções que retornam resultados aleatórios

    • Brian Genisio ressalta que zombar do seu RNG é uma opção para tornar seus testes repetíveis e fornece código de exemplo C #.
    • Mais uma vez, várias pessoas apontam para o uso de valores fixos de sementes para repetibilidade e testes simples para distribuição uniforme, qui-quadrado etc.
  3. A aleatoriedade do teste de unidade é um artigo do wiki que fala sobre muitos dos desafios já abordados ao tentar testar o que, por sua natureza, não é repetível. Uma parte interessante que eu recolhi foi a seguinte:

    Já vi o winzip usado como uma ferramenta para medir a aleatoriedade de um arquivo de valores antes (obviamente, quanto menor ele pode compactar o arquivo, menos aleatório é).

Bill the Lizard
fonte
Outro bom conjunto de testes para aleatoriedade estatística é o 'ent' encontrado em fourmilab.ch/random .
1
Você pode resumir alguns dos links que publicou para obter a resposta completa?
precisa saber é o seguinte
@ DanRasmussen Claro, terei tempo para fazer isso no fim de semana.
Bill the Lizard
4
“O problema com… a aleatoriedade é que não há um valor esperado…” - que irônico, dado que o “valor esperado” é um termo bem definido nas estatísticas. E embora não seja isso que você quis dizer, ele sugere a solução certa: usar propriedades conhecidas de distribuições estatísticas, juntamente com amostragem aleatória e testes estatísticos , para determinar se um algoritmo funciona com probabilidade muito alta. Sim, esse não é um teste de unidade clássico, mas eu queria mencioná-lo, pois, no caso mais fácil, apenas olha a distribuição do ... valor esperado .
Konrad Rudolph
2
Existe uma versão atualizada da famosa Bateria de Testes de Aleatoriedade Diehard na Dieharder, que inclui o Statistical Test Suite (STS) desenvolvido pelo Instituto Nacional de Padrões e Tecnologia (NIST). Está disponível pronto para execução no Ubuntu e provavelmente em outras distros: phy.duke.edu/~rgb/General/dieharder.php
nealmcb
21

1. Teste de unidade seu algoritmo

Para a primeira pergunta, eu criaria uma classe falsa que você alimenta uma sequência de números aleatórios para a qual você conhece o resultado do seu algoritmo. Dessa forma, você garante que o algoritmo que você constrói sobre sua função aleatória funcione. Então, algo como:

Random r = new RandomStub([1,3,5,3,1,2]);
r.random(); //returns 1
r.random(); //returns 3
...

2. Veja se sua função aleatória faz sentido

No teste de unidade, você deve adicionar um teste que é executado várias vezes e afirma que os resultados

  • estão dentro dos limites que você definiu (portanto, um lançamento de dados está entre 1 e 6) e
  • mostre uma distribuição sensata (faça várias execuções de teste e veja se a distribuição está dentro de x% do que você esperava, por exemplo, para a rolagem de dados, você verá um 2aumento entre 10% e 20% (1/6 = 16,67%) do tempo desde que você o tenha rolado 1000 vezes).

3. Teste de integração para o algoritmo e a função aleatória

Com que frequência você espera que sua matriz seja classificada na classificação original? Classifique algumas centenas de vezes e afirme que apenas x% do tempo a classificação não muda.

Na verdade, isso já é um teste de integração, você está testando o algoritmo junto com a função aleatória. Depois de usar a função aleatória real, você não poderá mais executar testes únicos.

Por experiência (escrevi um algoritmo genético), eu diria que combinar o teste de unidade do seu algoritmo, o teste de distribuição de sua função aleatória e o teste de integração é o caminho a seguir.

sebastiangeiger
fonte
14

Um aspecto dos PRNGs que parece esquecido é que todas as suas propriedades são de natureza estatística: você não pode esperar que o embaralhamento de uma matriz resulte em uma permutação diferente daquela com a qual você começou. Basicamente, se você estiver usando um PRNG normal, a única coisa garantida é que ele não use um padrão simples (espero) e que tenha distribuição uniforme entre o conjunto de números que retorna.

Um teste adequado para um PRNG envolve executá-lo pelo menos 100 vezes e, em seguida, verificar a distribuição da saída (que é uma resposta direta à segunda parte da pergunta).

Uma resposta para a primeira pergunta é quase a mesma: execute o teste cerca de 100 vezes com {1, 2, ..., n} e conte o número de vezes que cada elemento esteve em cada posição. Eles devem ser todos aproximadamente iguais se o método aleatório for bom.

Uma questão totalmente diferente é como testar os PRNGs de nível de criptografia. Este é um assunto em que você provavelmente não deve se deter, a menos que saiba realmente o que está fazendo. Sabe-se que as pessoas destroem (leia-se: abrir brechas catastróficas) bons sistemas de criptografia com apenas algumas 'otimizações' ou edições triviais.

EDIT: Reli completamente a pergunta, a resposta principal e a minha. Embora os argumentos que afirmo ainda permaneçam, eu responderia em segundo a resposta de Bill The Lizard. Os testes de unidade são booleanos por natureza - ou falham ou são bem-sucedidos e, portanto, não são adequados para testar "quão boas" são as propriedades de um PRNG (ou um método usando um PRNG), pois qualquer resposta a essa pergunta seria quantitativa , em vez de polar.

K.Steff
fonte
1
Eu acho que você quer dizer que o número de vezes que cada elemento está em cada posição deve ser aproximadamente igual. Se eles são consistentemente exatamente iguais, algo está muito errado.
outubro
Graças @octern, eu não sei como eu poderia ter escrito que ... foi completamente errado até agora ...
K.Steff
6

Existem duas partes para isso: testar a randomização e testar coisas que usam a randomização.

O teste da randomização é relativamente direto. Você verifica se o período do gerador de números aleatórios é o esperado (para algumas amostras usando algumas sementes meio aleatórias, dentro de algum limite) e que a distribuição da saída em um tamanho de amostra grande é a esperada deve estar (dentro de algum limite).

Testar coisas que usam a randomização é melhor realizado com um gerador de números psuedo-aleatórios determinístico. Como a saída da randomização é conhecida com base na semente (suas entradas), é possível realizar o teste de unidade normalmente com base nas entradas versus saídas esperadas. Se o seu RNG é não determinística, então zombar-lo com um que é determinística (ou simplesmente não aleatório). Teste a randomização isoladamente do código que a consome.

Telastyn
fonte
6

Deixe-o executar várias vezes e visualize seus dados .

Aqui está um exemplo de shuffle do Coding Horror , você pode ver que o algoritmo está bom ou não:

insira a descrição da imagem aqui

É fácil ver que todos os itens possíveis são retornados pelo menos uma vez (os limites estão corretos) e que a distribuição está correta.

Carra
fonte
1
A visualização de +1 é a chave. Eu sempre gostei do exemplo com a imagem de um pinguim na seção BCE do artigo de cifra de bloco ). Um software automatizado raramente pode detectar tais regularidades
Maksee
Eh? O objetivo dessa visualização é mostrar que a distribuição não está correta. O algoritmo de embaralhamento ingênuo torna certas ordens muito mais prováveis ​​que outras. Observe quanto mais à direita as barras 2341, 2314, 2143 e 1342 se estendem?
hvd 02/01
4

Indicadores gerais que achei úteis ao lidar com código que recebe entrada aleatória: Verifique os casos extremos da aleatoriedade esperada (valores máx. E mín. E máx. + 1 e min-1, se aplicável). Verifique os locais (ativado, acima e abaixo) onde os números têm pontos de inflexão (-1, 0, 1 ou maiores que 1, menores que 1 e não negativos para os casos em que um valor fracionário pode atrapalhar a função). Verifique alguns lugares completamente fora da entrada permitida. Verifique alguns casos típicos. Você também pode adicionar uma entrada aleatória, mas, para um teste de unidade que tenha o efeito colateral indesejável de que o mesmo valor não esteja sendo testado toda vez que o teste for executado (uma abordagem de semente pode funcionar, teste os primeiros 1.000 números aleatórios da semente) S ou algo assim).

Para testar a saída de uma função aleatória, é importante identificar o objetivo. No caso de cartões, o objetivo é testar a uniformidade do gerador aleatório 0-1, para determinar se todos os 52 cartões aparecem no resultado ou algum outro objetivo (talvez toda essa lista e mais)?

No exemplo específico, você deve assumir que seu gerador de números aleatórios é opaco (da mesma forma que não faz sentido testar a unidade syscall ou malloc do sistema operacional - a menos que você escreva sistemas operacionais). Pode ser útil medir o gerador de números aleatórios, mas seu objetivo não é escrever um gerador aleatório, apenas para ver que você recebe 52 cartas de cada vez e que elas mudam de ordem.

É um longo caminho para dizer que existem realmente duas tarefas de teste aqui: testar se o RNG está produzindo a distribuição correta e verificar se o código de reprodução aleatória do cartão está usando esse RNG para produzir resultados aleatórios. Se você estiver escrevendo o RNG, use a análise estatística para provar sua distribuição; se estiver escrevendo o shuffler de cartões, verifique se há 52 cartões não repetidos em cada saída (é o melhor caso para teste por inspeção que você está usando o RNG).

anon
fonte
4

Você pode confiar em geradores de números aleatórios seguros

Acabei de ter um pensamento horrível: você não está escrevendo seu próprio gerador de números aleatórios, está?

Supondo que você não seja, teste o código pelo qual você é responsável , e não o código de outras pessoas (como a SecureRandomimplementação da sua estrutura).

Testando seu código

Para testar se seu código responde corretamente, é normal usar um método de baixa visibilidade para produzir números aleatórios, para que ele possa ser facilmente substituído por uma classe de teste de unidade. Esse método substituído efetivamente zomba do gerador de números aleatórios e fornece controle total sobre o que é produzido e quando. Conseqüentemente, você pode exercitar completamente seu código, que é o objetivo do teste de unidade.

Obviamente, você verificará as condições das arestas e garantirá que o embaralhamento ocorra exatamente como o algoritmo determina, com as entradas apropriadas.

Testando o gerador seguro de números aleatórios

Se você não tiver certeza de que o gerador de números aleatórios seguro para o seu idioma não é verdadeiramente aleatório ou possui erros (fornece valores fora do intervalo, etc.), é necessário executar uma análise estatística detalhada da saída em várias centenas de milhões de iterações. Traçar a frequência de ocorrência de cada número e deve aparecer com igual probabilidade. Se os resultados se distorcerem de uma maneira ou de outra, você deve relatar suas descobertas aos designers da estrutura. Definitivamente, eles estarão interessados ​​em solucionar o problema, uma vez que os geradores de números aleatórios seguros são fundamentais para muitos algoritmos de criptografia.

Gary Rowe
fonte
1

Bem, você nunca estará 100% certo, então o melhor que você pode fazer é que é provável que os números sejam aleatórios. Escolha uma probabilidade - diga que uma amostra de números ou itens será exibida x vezes, com um milhão de amostras, dentro de uma margem de erro. Execute a coisa um milhão de vezes e veja se está dentro da margem. Felizmente, os computadores facilitam esse tipo de coisa.

Matthew Flynn
fonte
Mas testes de unidade como esse são considerados boas práticas ..? Sempre achei que um teste de unidade deveria ser o mais simples possível: sem loops, ramificações ou qualquer outra coisa que possa ser evitada.
Dlras2
4
Os testes de unidade devem estar corretos . Se for necessário ramificação, loops, recursão - esse é o preço. Você não pode testar em unidade classes extremamente sofisticadas e altamente otimizadas com testes de unidade de uma linha. Eu implementei o algoritmo de Dijkstra para testar uma classe uma vez.
275
3
@ K.Steff, uau. Você testou sua unidade para verificar se o algoritmo Dijkstra estava correto?
Winston Ewert
Bom ponto, de fato - sim, mas desta vez com testes 'triviais'. Eles também foram testes de unidade para o programa original (A *). Eu acho que é realmente uma boa prática - testar algoritmos rápidos contra implementações esfarrapadas (mas corretas).
31512 K.Steff
1

Para testar se uma fonte de números aleatórios está gerando algo que pelo menos tem a aparência de aleatoriedade, eu faria o teste gerar uma sequência bastante grande de bytes, gravá-los em um arquivo temporário e depois desembolsar para a ferramenta ent do Fourmilab . Insira a opção -t (concisa) para gerar um CSV fácil de analisar. Em seguida, verifique os vários números para ver se eles são "bons".

Para decidir quais números são bons, use uma fonte conhecida de aleatoriedade para calibrar seu teste. O teste quase sempre deve passar quando recebe um bom conjunto de números aleatórios. Como mesmo uma sequência verdadeiramente aleatória tem probabilidade de gerar uma sequência que parece não ser aleatória, não é possível fazer um teste que seja aprovado. Você apenas escolhe limites que tornam improvável que uma sequência aleatória cause uma falha no teste. A aleatoriedade não é divertida?

Nota: Você não pode escrever um teste que mostre que um PRNG gera uma sequência "aleatória". Você só pode escrever um teste que, se aprovado, indica alguma probabilidade de que a sequência gerada pelo PRNG seja "aleatória". Bem-vindo à alegria da aleatoriedade!

Wayne Conrad
fonte
1

Caso 1: testando um shuffle:

Considere uma matriz [0, 1, 2, 3, 4, 5], embaralhe-a, o que pode dar errado? As coisas usuais: a) sem embaralhar, b) embaralhar 1-5, mas não 0, embaralhar 0-4, mas não 5, embaralhar e sempre gerar o mesmo padrão, ...

Um teste para pegar todos:

Embaralhe 100 vezes, adicione os valores em cada slot. A soma de cada slot deve ser semelhante à outra. Média / Stddev pode ser calculada. (5 + 0) /2=2,5, 100 * 2,5 = 25. O valor esperado é de cerca de 25, por exemplo.

Se os valores estiverem fora do intervalo, há uma pequena chance de que você tenha um falso negativo. Você pode calcular o quão grande é essa chance. Repita o teste. Bem - é claro que há uma pequena chance de o teste falhar duas vezes seguidas. Mas você não tem uma rotina que exclua automaticamente sua fonte, se o teste de unidade falhar, não é? Execute-o novamente!

Pode falhar 3 vezes seguidas? Talvez você deva tentar a sorte na loteria.

Caso 2: Rolar um dado

A pergunta dos dados é a mesma. Jogue os dados 6000 vezes.

for (i in 0 to 6000) 
    ++slot [Random.nextInt (6)];
return (slot.max - slot.min) < threshold;
Usuário desconhecido
fonte