web-dev-qa-db-fra.com

Tests unitaires avec des fonctions qui renvoient des résultats aléatoires

Je ne pense pas que cela soit spécifique à un langage ou à un framework, mais j'utilise xUnit.net et C #.

J'ai une fonction qui renvoie une date aléatoire dans une certaine plage. Je passe une date et la date de retour se situe toujours entre 1 et 40 ans avant la date donnée.

Maintenant, je me demande s'il existe un bon moyen de tester cela à l'unité. La meilleure approche semble être de créer une boucle et de laisser la fonction s'exécuter 100 fois et d’affirmer que chacun de ces 100 résultats se situe dans la plage souhaitée, ce qui est mon approche actuelle.

Je réalise aussi que si je ne peux pas contrôler mon générateur Aléatoire, il n'y aura pas de solution parfaite (après tout, le résultat IS aléatoire), mais je me demande quelles approches vous prenez lorsque vous devez tester les fonctionnalités un résultat aléatoire dans une certaine plage?

65
Michael Stum

En plus de vérifier que la fonction renvoie une date dans la plage souhaitée, vous voulez vous assurer que le résultat est bien distribué. Le test que vous décrivez passera avec une fonction qui renvoie simplement la date que vous avez envoyée!

Donc, en plus d'appeler la fonction plusieurs fois et de vérifier que le résultat reste dans la plage souhaitée, j'essayerais également d'évaluer la distribution, peut-être en plaçant les résultats dans des compartiments et en vérifiant que les compartiments ont à peu près le même nombre de résultats terminé. Vous aurez peut-être besoin de plus de 100 appels pour obtenir des résultats stables, mais cela ne semble pas être une fonction coûteuse (en termes d'exécution), vous pouvez donc l'exécuter facilement pendant quelques K itérations.

J'ai déjà eu un problème avec des fonctions "aléatoires" non uniformes. Elles peuvent être très pénibles, cela vaut la peine de les tester plus tôt.

31
SquareCog

Simulez ou simulez le générateur de nombres aléatoires

Faites quelque chose comme ça ... Je ne l'ai pas compilé, il pourrait donc y avoir quelques erreurs de syntaxe.

public interface IRandomGenerator
{
    double Generate(double max);
}

public class SomethingThatUsesRandom
{
    private readonly IRandomGenerator _generator;

    private class DefaultRandom : IRandomGenerator
    {
        public double Generate(double max)
        {
            return (new Random()).Next(max);
        }
    }

    public SomethingThatUsesRandom(IRandomGenerator generator)
    {
        _generator = generator;
    }

    public SomethingThatUsesRandom() : this(new DefaultRandom())
    {}

    public double MethodThatUsesRandom()
    {
        return _generator.Generate(40.0);
    }
}

Dans votre test, il suffit de simuler ou d'imiter IRandomGenerator pour renvoyer quelque chose en conserve.

50
Brian Genisio

Je pense que vous testez trois aspects différents de ce problème. 

Le premier: mon algorithme est-il le bon? En d’autres termes, avec un générateur de nombres aléatoires fonctionnant correctement, produira-t-il des dates réparties de manière aléatoire dans la plage?

La seconde: l’algorithme gère-t-il correctement les cas Edge? Autrement dit, lorsque le générateur de nombres aléatoires produit les valeurs maximales ou minimales autorisées, quelque chose se brise-t-il?

Le troisième: ma mise en œuvre de l'algorithme fonctionne-t-elle? En d’autres termes, étant donné une liste connue d’entrées pseudo-aléatoires, produit-il la liste attendue de dates pseudo-aléatoires?

Les deux premières choses ne sont pas quelque chose que je construirais dans la suite de tests unitaires. Je les prouverais lors de la conception du système. Je le ferais probablement en écrivant un harnais de test générant des zillions de dates et en effectuant un test du khi-deux, comme le suggérait daniel.rikowski. Je m'assurerais également que ce faisceau de test ne se termine pas tant qu'il ne gère pas les deux cas Edge (en supposant que ma gamme de nombres aléatoires est suffisamment petite pour que je puisse m'en tirer à bon compte). Et je documenterais ceci, pour que quiconque s'avancerait et essayant d'améliorer l'algorithme sache que c'est un changement radical.

Le dernier est quelque chose pour lequel je ferais un test unitaire. J'ai besoin de savoir que rien n'a pénétré dans le code qui rompt la mise en œuvre de cet algorithme. Le premier signe que j'obtiendrai lorsque cela se produira sera que le test échouera. Ensuite, je reviens au code et découvre que quelqu'un d'autre a pensé qu'ils réparaient quelque chose et l'a cassé à la place. Si quelqu'un avait réparait l'algorithme, ce serait à eux de réparer également ce test.

9
Robert Rossney

Vous n'avez pas besoin de contrôler le système pour rendre les résultats déterministes. Vous êtes sur la bonne approche: déterminez ce qui est important pour le résultat de la fonction et testez-le. Dans ce cas, il est important que le résultat soit dans une plage de 40 jours et vous le testez. Il est également important de ne pas toujours obtenir le même résultat, alors testez-le également. Si vous voulez être plus chic, vous pouvez vérifier que les résultats passent un test aléatoire.

8
Ned Batchelder

Normalement, j'utilise exactement votre approche suggérée: contrôler le générateur aléatoire. Initialisez-le pour le test avec un germe par défaut (ou remplacez-le par un proxy renvoyant des nombres qui correspondent à mes cas de test), j'ai donc un comportement déterministe/testable.

5
flolo

Si vous voulez vérifier la qualité des nombres aléatoires (en termes d'indépendance), il existe plusieurs façons de le faire. Un bon moyen est le test du chi carré .

4
Daniel Rikowski

Selon la manière dont votre fonction crée la date aléatoire, vous pouvez également vérifier les dates illégales: années bissextiles impossibles ou 31ème jour d'un mois sur 30 jours.

2
mseery

Bien sûr, utiliser un générateur de nombres aléatoires à graine fixe fonctionnera très bien, mais même dans ce cas, vous essayez simplement de tester ce que vous ne pouvez pas prédire. Ce qui est ok C'est comme avoir un tas de tests fixes. Cependant, rappelez-vous - testez ce qui est important, mais n'essayez pas de tout tester. Je crois que les tests aléatoires sont un moyen d'essayer de tout tester, et que ce n'est pas efficace (ou rapide). Vous pourriez potentiellement avoir à exécuter un grand nombre de tests aléatoires avant de rencontrer un bogue.

Ce que j'essaie de comprendre, c’est que vous devriez simplement écrire un test pour chaque bogue que vous trouvez dans votre système. Vous testez les cas Edge pour vous assurer que votre fonction fonctionne même dans des conditions extrêmes, mais c'est vraiment ce que vous pouvez faire sans dépenser trop de temps, sans ralentir l'exécution des tests unitaires ou tout simplement en gaspillant des cycles de processeur.

2
thekingoftruth

Les méthodes qui ne présentent pas de comportement déterministe ne peuvent pas être testées correctement, car les résultats différeront d'une exécution à l'autre. Une façon de contourner ce problème consiste à graine le générateur de nombres aléatoires avec une valeur fixe pour le test unitaire. Vous pouvez également extraire le caractère aléatoire de la classe de génération de date (et appliquer ainsi le principe de responsabilité unique ), et injecter des valeurs connues pour les tests unitaires.

2
philant

Je recommanderais de remplacer la fonction aléatoire. Je fais des tests unitaires dans PHP, alors j'écris ce code:

// If we are unit testing, then...
if (defined('UNIT_TESTING') && UNIT_TESTING)
{
   // ...make our my_Rand() function deterministic to aid testing.
   function my_Rand($min, $max)
   {
      return $GLOBALS['random_table'][$min][$max];
   }
}
else
{
   // ...else make our my_Rand() function truly random.
   function my_Rand($min = 0, $max = PHP_INT_MAX)
   {
      if ($max === PHP_INT_MAX)
      {
         $max = getrandmax();
      }
      return Rand($min, $max);
   }
}

Je règle ensuite random_table selon mes besoins pour chaque test.

Tester le véritable caractère aléatoire d'une fonction aléatoire est un test à part entière. J'éviterais de tester le caractère aléatoire dans les tests unitaires, mais plutôt de faire des tests séparés et de rechercher le véritable caractère aléatoire de la fonction aléatoire dans le langage de programmation que vous utilisez. Les tests non déterministes (le cas échéant) doivent être exclus des tests unitaires. Peut-être avez-vous une suite séparée pour ces tests, qui nécessite une intervention humaine ou des temps d'exécution beaucoup plus longs afin de minimiser les risques d'échec.

1
user263976

Je ne pense pas que les tests unitaires soient conçus pour cela. Vous pouvez utiliser les tests unitaires pour les fonctions qui renvoient une valeur stochastique, mais utilisez une valeur de départ fixe, auquel cas, d'une manière différente, ils ne sont pas stochastiques, pour ainsi dire, Pour une valeur aléatoire, je ne pense pas que les tests unitaires soient ce que vous voulez. Par exemple, pour les GNA, vous voulez avoir un test système, dans lequel vous exécutez le GNA plusieurs fois et regardez la distribution ou les moments de celle-ci.

0
ddd