J'ai une classe destinée à générer un mot de passe aléatoire d'une longueur également aléatoire, mais limité entre une longueur minimale et maximale définie.
Je construis des tests unitaires et j'ai rencontré un petit problème intéressant avec cette classe. L’idée d’un test unitaire est qu’il soit répétable. Si vous exécutez le test cent fois, il devrait donner les mêmes résultats cent fois. Si vous dépendez d'une ressource qui peut ou peut ne pas être là ou peut ne pas être dans l'état initial que vous attendez, alors vous êtes censé vous moquer de la ressource en question pour vous assurer que votre test est vraiment toujours répétable.
Mais qu'en est-il dans les cas où le SUT est censé générer une sortie indéterminée?
Si je fixe la longueur minimale et maximale à la même valeur, je peux facilement vérifier que le mot de passe généré est de la longueur attendue. Mais si je spécifie une plage de longueurs acceptables (par exemple, 15 à 20 caractères), vous avez maintenant le problème suivant: vous pouvez exécuter le test cent fois et obtenir 100 passages, mais à la 101ème exécution, vous pourriez obtenir une chaîne de 9 caractères.
Dans le cas de la classe de mot de passe, qui est assez simple en son cœur, cela ne devrait pas être un gros problème. Mais cela m'a fait penser au cas général. Quelle est la stratégie généralement acceptée comme la meilleure lorsqu’il s’agit de SEU générant une sortie indéterminée par conception?
la source
Réponses:
Les sorties "non déterministes" devraient pouvoir devenir déterministes aux fins des tests unitaires. Une façon de gérer le caractère aléatoire consiste à permettre le remplacement du moteur aléatoire. Voici un exemple (PHP 5.3+):
Vous pouvez créer une version de test spécialisée de la fonction qui renvoie toute séquence de chiffres que vous souhaitez vous assurer que le test est entièrement répétable. Dans le programme réel, vous pouvez avoir une implémentation par défaut qui pourrait être la solution de secours si elle n’est pas remplacée.
la source
Le mot de passe de sortie réel peut ne pas être déterminé à chaque fois que la méthode est exécutée, mais il conservera néanmoins des fonctionnalités déterminées pouvant être testées, telles que la longueur minimale, les caractères compris dans un jeu de caractères déterminé, etc.
Vous pouvez également vérifier que la routine renvoie un résultat déterminé à chaque fois en configurant votre générateur de mot de passe avec la même valeur à chaque fois.
la source
Test contre "le contrat". Lorsque la méthode est définie comme "génère des mots de passe de 15 à 20 caractères avec un z", testez-le de cette façon
De plus, vous pouvez extraire la génération, de sorte que tout ce qui en dépend puisse être testé en utilisant une autre classe de générateur "statique"
la source
Vous avez une
Password generator
et vous avez besoin d'une source aléatoire.Comme vous l'avez dit dans la question, a
random
crée une sortie non déterministe car il s'agit d' un état global . Cela signifie qu’il accède à quelque chose en dehors du système pour générer des valeurs.Vous ne pouvez jamais vous débarrasser de quelque chose comme ça pour toutes vos classes, mais vous pouvez séparer la génération de mot de passe pour la création de valeurs aléatoires.
Si vous structurez le code de cette manière, vous pouvez vous en moquer
RandomSource
pour vos tests.Vous ne pourrez pas le tester à 100%,
RandomSource
mais les suggestions que vous avez reçues pour tester les valeurs de cette question pourront lui être appliquées (comme les tests quirand->(1,26);
renvoient toujours un nombre compris entre 1 et 26.la source
Dans le cas d’un Monte Carlo de physique des particules, j’ai écrit des "tests unitaires" {*} qui invoquent la routine non déterministe avec un germe aléatoire prédéfini , puis j’exécute un nombre statistique de fois et vérifie les violations de contraintes (niveaux d’énergie). au-dessus de l'énergie d'entrée doit être inaccessible, tous les passages doivent sélectionner un niveau, etc.) et des régressions par rapport aux résultats précédemment enregistrés.
{*} Un tel test enfreint le principe du "test rapide" pour les tests unitaires, vous pouvez donc vous sentir mieux de les caractériser d'une autre manière: tests d'acceptation ou de régression, par exemple. Pourtant, j'ai utilisé mon framework de tests unitaires.
la source
Je ne suis pas d'accord avec la réponse acceptée , pour deux raisons:
(Notez que cela peut être une bonne réponse dans de nombreuses circonstances, mais pas du tout, et peut-être pas du tout.)
Alors qu'est-ce que je veux dire par là? Par surapprentissage, nous entendons un problème typique des tests statistiques: il survient lorsque vous testez un algorithme stochastique par rapport à un ensemble de données trop contraint. Si vous revenez ensuite et affinez votre algorithme, vous l'aurez implicitement bien ajusté aux données d'entraînement (vous aurez accidentellement ajusté votre algorithme aux données de test), mais toutes les autres données ne seront peut-être pas du tout (car vous ne les testez jamais). .
(Incidemment, il s'agit toujours d'un problème persistant dans les tests unitaires. C'est pourquoi de bons tests sont complets , ou du moins représentatifs pour une unité donnée, et c'est difficile en général.)
Si vous faites vos tests déterministes en rendant le générateur de nombres aléatoires enfichable, vous effectuez toujours des tests sur le même ensemble de données très petit et (généralement) non représentatif . Cela déforme vos données et peut entraîner des biais dans votre fonction.
Le deuxième point, impraticabilité, se pose lorsque vous n’avez aucun contrôle sur la variable stochastique. Cela ne se produit généralement pas avec les générateurs de nombres aléatoires (sauf si vous avez besoin d'une "vraie" source de données aléatoires), mais cela peut arriver lorsque les stochastiques se faufilent dans votre problème par d'autres moyens. Par exemple, lorsque vous testez du code concurrent: les conditions de concurrence sont toujours stochastiques, vous ne pouvez pas (facilement) les rendre déterministes.
Le seul moyen de renforcer la confiance dans ces cas est de tester beaucoup . Faire mousser, rincer, répéter. Cela augmente la confiance, jusqu'à un certain niveau (à quel point le compromis pour des tests supplémentaires devient négligeable).
la source
Vous avez en fait de multiples responsabilités ici. Les tests unitaires et en particulier le TDD sont parfaits pour mettre en évidence ce genre de chose.
Les responsabilités sont:
1) Générateur de nombres aléatoires. 2) Formateur de mot de passe.
Le formateur de mot de passe utilise le générateur de nombres aléatoires. Injectez le générateur dans votre formateur via son constructeur en tant qu'interface. Vous pouvez maintenant tester complètement votre générateur de nombre aléatoire (test statistique) et tester le formateur en injectant un générateur de nombre aléatoire simulé.
Non seulement vous obtenez un meilleur code, vous obtenez de meilleurs tests.
la source
Comme les autres l'ont déjà mentionné, vous devez tester ce code en supprimant le caractère aléatoire.
Vous pouvez également avoir un test de niveau supérieur qui laisse le générateur de nombres aléatoires en place, teste uniquement le contrat (longueur du mot de passe, caractères autorisés, ...) et, en cas d'échec, sauvegarde suffisamment d'informations pour vous permettre de reproduire le système. Etat dans l'un des cas où le test aléatoire a échoué.
Peu importe que le test lui-même ne soit pas reproductible - tant que vous pouvez trouver la raison pour laquelle il a échoué cette fois.
la source
De nombreuses difficultés de tests unitaires deviennent triviales lorsque vous refactorisez votre code pour rompre les dépendances. Une base de données, un système de fichiers, l’utilisateur ou, dans votre cas, une source d’aléatoire.
Une autre façon de voir les choses est que les tests unitaires sont supposés répondre à la question "ce code fait-il ce que je compte faire?". Dans votre cas, vous ne savez pas ce que vous voulez que le code fasse, car il n’est pas déterministe.
Dans cet esprit, séparez votre logique en petites pièces faciles à comprendre, à comprendre et à tester. Plus précisément, vous créez une méthode distincte (ou classe!) Qui prend une source aléatoire en entrée et génère le mot de passe en sortie. Ce code est clairement déterministe.
Dans votre test unitaire, vous lui communiquez la même entrée pas très aléatoire à chaque fois. Pour les très petits flux aléatoires, il suffit de coder en dur les valeurs de votre test. Sinon, fournissez une valeur constante au RNG dans votre test.
À un niveau de test plus élevé (appelez-le "acceptation" ou "intégration" ou autre), vous laisserez le code s'exécuter avec une véritable source aléatoire.
la source
La plupart des réponses ci-dessus indiquent que la voie à suivre est de se moquer du générateur de nombres aléatoires, mais j’utilisais simplement la fonction intégrée mt_rand. Permettre de se moquer aurait signifié réécrire la classe pour exiger l'injection d'un générateur de nombres aléatoires au moment de la construction.
Ou alors j'ai pensé!
L'une des conséquences de l'ajout d'espaces de noms est que le moquage construit dans les fonctions PHP est devenu incroyablement difficile à trivialement simple. Si le SUT se trouve dans un espace de noms donné, il vous suffit de définir votre propre fonction mt_rand dans le test unitaire sous cet espace de noms. Ce dernier sera utilisé à la place de la fonction PHP intégrée pour la durée du test.
Voici la suite de tests finalisée:
Je pensais en parler, car redéfinir les fonctions internes de PHP est une autre utilisation d’espaces de noms qui ne m’était tout simplement pas venu à l’esprit. Merci à tous pour l'aide avec cela.
la source
Il convient d'inclure un test supplémentaire dans cette situation, qui permet de s'assurer que les appels répétés au générateur de mot de passe génèrent des mots de passe différents. Si vous avez besoin d'un générateur de mot de passe thread-safe, vous devez également tester les appels simultanés à l'aide de plusieurs threads.
Cela garantit essentiellement que vous utilisez correctement votre fonction aléatoire et que vous ne réintroduisez pas à chaque appel.
la source