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:
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 ...
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.
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:
Comment tester à l'unité un générateur de nombres pseudo aléatoires?
Test unitaire avec des fonctions qui retournent des résultats aléatoires
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).
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
...
Au test unitaire, vous devez ajouter un test qui s'exécute plusieurs fois et affirme que les résultats
2
montez entre 10% et 20% (1/6 = 16,67%) du temps étant donné que vous l'avez roulé 1000 fois).À 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.
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.
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:
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.
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.
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).
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.
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.
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!
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;