web-dev-qa-db-fra.com

Comment tester l'aléatoire?

Considérons une méthode pour mélanger aléatoirement des éléments dans un tableau. Comment écririez-vous un test unitaire simple mais robuste pour vous assurer que cela fonctionne?

J'ai trouvé deux idées, qui ont toutes deux des défauts notables:

  • Mélangez le tableau, puis assurez-vous que son ordre diffère d'avant. Cela semble bon, mais échoue si le shuffle arrive à shuffle dans le même ordre. (Improbable, mais possible.)
  • Mélangez le tableau avec une valeur de départ constante et comparez-le à la sortie prédéterminée. Cela repose sur la fonction aléatoire renvoyant toujours les mêmes valeurs étant donné la même graine. Cependant, c'est parfois un hypothèse invalide .

Prenons une deuxième fonction qui simule les lancers de dés et renvoie un nombre aléatoire. Comment testeriez-vous cette fonction? Comment testeriez-vous que la fonction ...

  • ne renvoie jamais un nombre en dehors des limites données?
  • renvoie des nombres dans une distribution valide? (Uniforme pour un dé, normal pour un grand nombre de dés.)

Je cherche des réponses offrant un aperçu des tests non seulement de ces exemples, mais des éléments aléatoires de code en général. Les tests unitaires sont-ils même la bonne solution ici? Sinon, quels types de tests sont-ils?


Juste pour apaiser l'esprit de tout le monde, je n'écris pas mon propre générateur de nombres aléatoires.

135
dlras2

Je ne pense pas que les tests unitaires soient le bon outil pour tester le caractère aléatoire. Un test unitaire doit appeler une méthode et tester la valeur retournée (ou l'état de l'objet) par rapport à une valeur attendue. Le problème avec le test aléatoire est qu'il n'y a pas de valeur attendue pour la plupart des choses que vous souhaitez tester. Vous pouvez tester avec une graine donnée, mais cela teste uniquement la répétabilité . Cela ne vous donne aucun moyen de mesurer le degré de aléatoire de la distribution, ou même si elle est aléatoire.

Heureusement, il existe de nombreux tests statistiques, tels que le Diehard Battery of Tests of Randomness . Voir également:

  1. Comment tester à l'unité un générateur de nombres pseudo aléatoires?

    • Steve Jessop vous recommande de trouver une implémentation testée du même algorithme RNG que vous utilisez et de comparer sa sortie avec les graines sélectionnées par rapport à votre propre implémentation.
    • Greg Hewgill recommande la suite ENT de tests statistiques.
    • John D. Cook renvoie les lecteurs à son article CodeProject Simple Random Number Generation , qui inclut une implémentation du test de Kolmogorov-Smirnov mentionné dans le volume 2 de Donald Knuth, Seminumerical Algorithms.
    • Plusieurs personnes recommandent de tester que la distribution des nombres générés est uniforme, le test du chi carré et de tester que la moyenne et l'écart-type sont dans la plage attendue. (Notez que tester la distribution seule ne suffit pas. [1,2,3,4,5,6,7,8] est une distribution uniforme, mais ce n'est certainement pas aléatoire.)
  2. Test unitaire avec des fonctions qui retournent des résultats aléatoires

    • Brian Genisio souligne que se moquer de votre RNG est une option pour rendre vos tests reproductibles et fournit un exemple de code C #.
    • Encore une fois, plusieurs personnes soulignent l'utilisation de valeurs de graines fixes pour la répétabilité et de tests simples pour une distribution uniforme, le chi carré, etc.
  3. nit Testing Randomness est un article wiki qui parle de nombreux défis déjà abordés lorsque vous essayez de tester ce qui, par nature, n'est pas répétable. Un morceau intéressant que j'en ai tiré était le suivant:

    J'ai déjà vu winzip utilisé comme un outil pour mesurer le caractère aléatoire d'un fichier de valeurs (évidemment, plus il peut compresser le fichier, moins il est aléatoire).

104
Bill the Lizard

1. Test unitaire de votre algorithme

Pour la première question, je construirais une fausse classe que vous alimentez une séquence de nombres aléatoires dont vous connaissez le résultat de votre algorithme. De cette façon, vous vous assurez que l'algorithme que vous construisez en haut de votre fonction aléatoire fonctionne. Donc, quelque chose dans le sens de:

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

2. Voyez si votre fonction aléatoire est logique

Au test unitaire, vous devez ajouter un test qui s'exécute plusieurs fois et affirme que les résultats

  • sont dans les limites que vous définissez (donc, un lancer de dés se situe entre 1 et 6) et
  • afficher une distribution raisonnable (faites plusieurs tests et voyez si la distribution est à x% de ce que vous attendiez, par exemple pour le lancer de dés, vous devriez voir un 2 montez entre 10% et 20% (1/6 = 16,67%) du temps étant donné que vous l'avez roulé 1000 fois).

3. Test d'intégration pour l'algorithme et la fonction aléatoire

À quelle fréquence vous attendriez-vous à ce que votre tableau soit trié dans le tri d'origine? Triez quelques centaines de fois et affirmez que seulement x% du temps, le tri ne change pas.

Il s'agit en fait déjà d'un test d'intégration, vous testez l'algorithme avec la fonction aléatoire. Une fois que vous utilisez la vraie fonction aléatoire, vous ne pouvez plus vous en sortir avec des tests uniques.

Par expérience (j'ai écrit un algorithme génétique), je dirais que combiner le test unitaire de votre algorithme, le test de distribution de votre fonction aléatoire et le test d'intégration est la voie à suivre.

21
sebastiangeiger

Un aspect des PRNG qui semble oublié est que toutes ses propriétés sont de nature statistique: vous ne pouvez pas vous attendre à ce que le mélange d'un tableau entraîne une permutation différente de celle avec laquelle vous avez commencé. Fondamentalement, si vous utilisez un PRNG normal, la seule chose que vous êtes garanti est qu'il n'utilise pas un modèle simple (espérons-le) et qu'il a une distribution égale entre l'ensemble de nombres qu'il renvoie.

Un bon test pour un PRNG impliquera de l'exécuter au moins 100 fois puis de vérifier la distribution de la sortie (ce qui est une réponse directe à la deuxième partie de la question).

La réponse à la première question est presque la même: exécutez le test environ 100 fois avec {1, 2, ..., n} et comptez le nombre de fois que chaque élément a été à chaque position. Ils devraient tous être à peu près égaux si la méthode de lecture aléatoire est bonne.

Une toute autre question est de savoir comment tester les PRNG de qualité cryptographique. C'est une question sur laquelle vous ne devriez probablement pas vous attarder, sauf si vous savez vraiment ce que vous faites. Les gens sont connus pour détruire (lire: ouvrir des trous catastrophiques dans) de bons cryptosystèmes avec seulement quelques optimisations ou modifications triviales.

EDIT: J'ai relu à fond la question, la première réponse et la mienne. Tandis que les points que je soulève sont toujours valables, j'appuie la réponse de Bill The Lizard. Les tests unitaires sont de nature booléenne - ils échouent ou ils réussissent, et ne sont donc pas adaptés pour tester "à quel point" sont les propriétés d'un PRNG (ou une méthode utilisant un PRNG), car toute réponse à cette question serait quantitative, plutôt que polaire.

15
K.Steff

Laissez-le s'exécuter plusieurs fois et visualisez vos données .

Voici un exemple de lecture aléatoire de Coding Horror , vous pouvez voir que l'algorithme est OK ou non:

enter image description here

Il est facile de voir que chaque élément possible est retourné au moins une fois (les limites sont OK) et que la distribution est OK.

7
Carra

Il y a deux parties: tester la randomisation et tester les choses qui utilisent la randomisation.

Le test de randomisation est relativement simple. Vous vérifiez que la période du générateur de nombres aléatoires est celle que vous attendez (pour quelques échantillons utilisant quelques graines un peu aléatoires, dans un certain seuil) et que la distribution de la sortie sur une grande taille d'échantillon est comme vous vous y attendez ce soit (dans un certain seuil).

Il est préférable de tester les choses qui utilisent la randomisation avec un générateur de nombres déterministes pseudo-aléatoires. Étant donné que la sortie de la randomisation est connue en fonction de la graine (ses entrées), vous pouvez alors effectuer un test unitaire normal en fonction des entrées par rapport aux sorties attendues. Si votre RNG est pas déterministe, alors moquez-le avec un qui est déterministe (ou tout simplement pas aléatoire). Testez la randomisation indépendamment du code qui la consomme.

6
Telastyn

Pointeurs généraux que j'ai trouvés utiles lors du traitement de code qui prend une entrée aléatoire: vérifiez les cas Edge du caractère aléatoire attendu (valeurs max et min, et les valeurs max + 1 et min-1 le cas échéant). Vérifiez les endroits (activé, supérieur et inférieur) où les nombres ont des points d'inflexion (c'est-à-dire -1, 0, 1 ou supérieur à 1, inférieur à 1 et non négatif pour les cas où une valeur fractionnaire pourrait gâcher la fonction). Vérifiez quelques endroits complètement en dehors de l'entrée autorisée. Vérifiez quelques cas typiques. Vous pouvez également ajouter une entrée aléatoire, mais pour un test unitaire qui a l'effet secondaire indésirable que la même valeur n'est pas testée à chaque fois que le test est exécuté (une approche de départ peut cependant fonctionner, testez les 1000 premiers nombres aléatoires de départ S ou somesuch).

Pour tester la sortie d'une fonction aléatoire, il est important d'identifier l'objectif. Dans le cas des cartes, l'objectif est-il de tester l'uniformité du générateur aléatoire 0-1, pour déterminer si les 52 cartes apparaissent dans le résultat, ou un autre objectif (peut-être toute cette liste et plus)?

Dans l'exemple spécifique, vous devez supposer que votre générateur de nombres aléatoires est opaque (tout comme il n'est pas logique de tester uniquement le système OS ou malloc - sauf si vous écrivez des systèmes d'exploitation). Il peut être utile de mesurer le générateur de nombres aléatoires, mais votre objectif n'est pas d'écrire un générateur aléatoire, juste pour voir que vous obtenez 52 cartes à chaque fois, et qu'elles changent d'ordre.

C'est une longue façon de dire qu'il y a vraiment deux tâches de test ici: tester que le RNG produit la bonne distribution et vérifier que le code de lecture aléatoire de votre carte utilise ce RNG pour produire des résultats aléatoires. Si vous écrivez le RNG, utilisez une analyse statistique pour prouver votre distribution, si vous écrivez le mélangeur de cartes, assurez-vous qu'il y a 52 cartes non répétées dans chaque sortie (c'est un meilleur cas pour tester par inspection que vous utilisez le RNG).

4
anon

Vous pouvez compter sur des générateurs de nombres aléatoires sécurisés

Je viens d'avoir une horrible pensée: vous n'écrivez pas votre propre générateur de nombres aléatoires, n'est-ce pas?

En supposant que vous ne l'êtes pas, vous devriez alors tester le code dont vous êtes responsable, pas le code des autres (comme l'implémentation SecureRandom pour votre framework).

Test de votre code

Pour tester que votre code répond correctement, il est normal d'utiliser une méthode de faible visibilité pour produire les nombres aléatoires afin qu'il puisse être facilement remplacé par une classe de test unitaire. Cette méthode redéfinie se moque efficacement du générateur de nombres aléatoires et vous donne un contrôle complet sur ce qui est produit et quand. Par conséquent, vous pouvez exercer pleinement votre code, ce qui est l'objectif des tests unitaires.

De toute évidence, vous vérifierez les conditions Edge et vous assurerez que le brassage a lieu exactement comme votre algorithme le requiert, compte tenu des entrées appropriées.

Test du générateur de nombres aléatoires sécurisé

Si vous n'êtes pas certain que le générateur de nombres aléatoires sécurisé pour votre langue n'est pas vraiment aléatoire ou est bogué (fournit des valeurs hors plage, etc.), vous devez effectuer une analyse statistique détaillée de la sortie sur plusieurs centaines de millions d'itérations. Tracez la fréquence d'occurrence de chaque nombre et il devrait apparaître avec une probabilité égale. Si les résultats sont biaisés d'une manière ou d'une autre, vous devez signaler vos résultats aux concepteurs de framework. Ils seront certainement intéressés à résoudre le problème car les générateurs de nombres aléatoires sécurisés sont fondamentaux pour de nombreux algorithmes de chiffrement.

4
Gary Rowe

Eh bien, vous ne serez jamais certain à 100%, donc le mieux que vous puissiez faire est qu'il est probable que les nombres soient aléatoires. Choisissez une probabilité - disons qu'un échantillon de nombres ou d'articles arrivera x fois pour un million d'échantillons, dans une marge d'erreur. Exécutez la chose un million de fois et voyez si elle est dans la marge. Heureusement, les ordinateurs facilitent ce genre de choses.

1
Matthew Flynn

Pour tester qu'une source de nombres aléatoires génère quelque chose qui a au moins l'apparence d'un caractère aléatoire, je voudrais que le test génère une séquence d'octets assez grande, les écrive dans un fichier temporaire, puis Shell vers Fourmilab's ent outil. Donnez à l'ent le commutateur -t (laconique) pour qu'il génère un fichier CSV facile à analyser. Vérifiez ensuite les différents nombres pour voir s'ils sont "bons".

Pour décider quels nombres sont bons, tilisez une source connue d'aléatoire pour calibrer votre test. Le test doit presque toujours réussir lorsqu'il reçoit un bon ensemble de nombres aléatoires. Parce que même une séquence vraiment aléatoire a une probabilité de générer une séquence qui semble non aléatoire, vous ne pouvez pas obtenir un test qui est certain de passer. Vous choisissez simplement des seuils qui rendent peu probable qu'une séquence aléatoire entraîne un échec du test. Le hasard n'est-il pas amusant?

Remarque: Vous ne pouvez pas écrire un test qui montre qu'un PRNG génère une séquence "aléatoire". Vous ne pouvez écrire un test qui, s'il réussit, indique une certaine probabilité que la séquence générée par le = PRNG est "aléatoire". Bienvenue dans la joie du hasard!

1
Wayne Conrad

Cas 1: Test d'un shuffle:

Considérez un tableau [0, 1, 2, 3, 4, 5], mélangez-le, qu'est-ce qui peut mal tourner? Les trucs habituels: a) pas de mélange du tout, b) mélange 1-5 mais pas 0, mélange 0-4 mais pas 5, mélange et générant toujours le même motif, ...

Un test pour les attraper tous:

Mélangez 100 fois, ajoutez les valeurs dans chaque emplacement. La somme de chaque emplacement doit être similaire à chaque autre emplacement. Avg/Stddev peut être calculé. (5 + 0) /2=2,5, 100 * 2,5 = 25. La valeur attendue est d'environ 25, par exemple.

Si les valeurs sont hors limites, il y a une petite chance que vous obteniez un faux négatif. Vous pouvez calculer la taille de cette chance. Répétez le test. Eh bien - bien sûr, il y a une petite chance que le test échoue 2 fois de suite. Mais vous n'avez pas de routine qui supprime automatiquement votre source, si le test unitaire échoue, n'est-ce pas? Exécutez-le à nouveau!

Il peut échouer 3 fois de suite? Vous devriez peut-être tenter votre chance à la loterie.

Cas 2: lancez un dé

La question du lancer de dés est la même question. Lancez les dés 6000 fois.

for (i in 0 to 6000) 
    ++slot [Random.nextInt (6)];
return (slot.max - slot.min) < threshold;
1
user unknown