Données aléatoires dans les tests unitaires?

136

J'ai un collègue qui écrit des tests unitaires pour des objets qui remplissent leurs champs avec des données aléatoires. Sa raison est qu'il donne une gamme de tests plus large, car il testera un grand nombre de valeurs différentes, alors qu'un test normal n'utilise qu'une seule valeur statique.

Je lui ai donné un certain nombre de raisons différentes contre cela, les principales étant:

  • des valeurs aléatoires signifie que le test n'est pas vraiment répétable (ce qui signifie également que si le test peut échouer de manière aléatoire, il peut le faire sur le serveur de construction et interrompre la construction)
  • si c'est une valeur aléatoire et que le test échoue, nous devons a) réparer l'objet et b) nous forcer à tester cette valeur à chaque fois, donc nous savons que cela fonctionne, mais comme c'est aléatoire, nous ne savons pas quelle était la valeur

Un autre collègue a ajouté:

  • Si je teste une exception, les valeurs aléatoires ne garantiront pas que le test se termine dans l'état attendu
  • des données aléatoires sont utilisées pour vider un système et les tests de charge, pas pour les tests unitaires

Quelqu'un d'autre peut-il ajouter des raisons supplémentaires que je peux lui donner pour qu'il arrête de faire cela?

(Ou bien, est-ce une méthode acceptable pour écrire des tests unitaires, et moi et mon autre collègue avons tort?)

Adam V
la source
32
"des valeurs aléatoires signifie que le test n'est pas vraiment répétable" n'est pas vrai, car les tets utiliseront des nombres pseudo-aléatoires. Fournissez la même graine initiale, obtenez la même séquence de tests «aléatoires».
Raedwald
11
Anecdote: j'ai écrit une fois une classe d'exportation CSV, et des tests aléatoires ont révélé un bogue lorsque des caractères de contrôle étaient placés à la fin d'une cellule. Sans tests aléatoires, je n'aurais jamais pensé à ajouter cela comme cas de test. At-il toujours échoué? Non. Est-ce un test parfait? Non. Cela m'a-t-il aidé à détecter et à corriger un bug? Oui.
Tyzoid
1
Les tests peuvent également servir de documentation, pour expliquer quand le code attend en entrée et ce qui est attendu en sortie. Avoir un test avec des données arbitraires claires peut être plus simple et plus explicatif que du code qui génère des données aléatoires.
splintor
Si votre test unitaire échoue en raison d'une valeur générée aléatoirement et que cette valeur ne fait pas partie d'une assertion, bonne chance avec le débogage de votre test unitaire.
eriksmith200

Réponses:

72

Il y a un compromis. Votre collègue est en fait sur quelque chose, mais je pense qu'il le fait mal. Je ne suis pas sûr qu'un test totalement aléatoire soit très utile, mais ce n'est certainement pas invalide.

Une spécification de programme (ou d'unité) est une hypothèse selon laquelle il existe un programme qui y répond. Le programme lui-même est alors la preuve de cette hypothèse. Ce que les tests unitaires devraient être, c'est une tentative de fournir des contre-preuves pour réfuter que le programme fonctionne selon les spécifications.

Maintenant, vous pouvez écrire les tests unitaires à la main, mais c'est vraiment une tâche mécanique. Il peut être automatisé. Tout ce que vous avez à faire est d'écrire la spécification, et une machine peut générer beaucoup de tests unitaires qui tentent de casser votre code.

Je ne sais pas quelle langue vous utilisez, mais voyez ici:

Java http://functionaljava.org/

Scala (ou Java) http://github.com/rickynils/scalacheck

Haskell http://www.cs.chalmers.se/~rjmh/QuickCheck/

.NET: http://blogs.msdn.com/dsyme/archive/2008/08/09/fscheck-0-2.aspx

Ces outils prendront votre spécification bien formée en entrée et généreront automatiquement autant de tests unitaires que vous le souhaitez, avec des données générées automatiquement. Ils utilisent des stratégies de "rétrécissement" (que vous pouvez modifier) ​​pour trouver le cas de test le plus simple possible pour casser votre code et s'assurer qu'il couvre bien les cas de bord.

Bon test!

Apocalisp
la source
1
+1 à cela. ScalaCheck fait un travail phénoménal de génération de données de test aléatoires minimisées de manière répétable.
Daniel Spiewak
17
Ce n'est pas aléatoire. C'est arbitraire. Grande différence :)
Apocalisp
reductiotest.org semble maintenant plus exister, et Google ne m'a pointé nulle part ailleurs. Une idée de la situation actuelle?
Raedwald
Il fait désormais partie de la bibliothèque Functional Java. Lien modifié. Mais j'utiliserais juste Scalacheck pour tester le code Java.
Apocalisp le
ScalaCheck a déménagé sur GitHub. Lien mis à jour dans la réponse. Il est également utile pour Java, pas seulement pour Scala. (L'ancien lien était code.google.com/p/scalacheck )
RobertB
38

Ce type de test s'appelle un test Monkey . Lorsqu'il est bien fait, il peut fumer des insectes dans les coins vraiment sombres.

Pour répondre à vos préoccupations concernant la reproductibilité: la bonne façon d'aborder cela, est d'enregistrer les entrées de test ayant échoué, de générer un test unitaire, qui sonde pour toute la famille du bogue spécifique; et incluez dans le test unitaire la seule entrée spécifique (à partir des données aléatoires) qui a causé l'échec initial.

Dragon d'argent
la source
26

Il y a une maison de transition ici qui a une certaine utilité, qui consiste à ensemencer votre PRNG avec une constante. Cela vous permet de générer des données «aléatoires» qui sont répétables.

Personnellement, je pense qu'il y a des endroits où des données aléatoires (constantes) sont utiles pour les tests - une fois que vous pensez avoir fait tous vos recoins soigneusement réfléchis, l'utilisation de stimuli provenant d'un PRNG peut parfois trouver d'autres choses.

Will Dean
la source
4
J'ai vu cela bien fonctionner sur un système qui avait beaucoup de verrouillage et de threads. La graine «aléatoire» était écrite dans un fichier à chaque exécution, puis si une exécution échouait, nous pourrions déterminer le chemin emprunté par le code et écrire un test unitaire écrit à la main pour ce cas que nous avions manqué.
Ian Ringrose
Que signifie PRNG?
systemovich
Générateur de nombres pseudo aléatoires
Will Dean
16

Dans le livre Beautiful Code , il y a un chapitre intitulé "Beautiful Tests", où il passe par une stratégie de test pour l' algorithme de recherche binaire . Un paragraphe est intitulé "Actes aléatoires de test", dans lequel il crée des tableaux aléatoires pour tester en profondeur l'algorithme. Vous pouvez en lire une partie en ligne sur Google Livres, page 95 , mais c'est un excellent livre qui vaut la peine d'être lu .

Donc, fondamentalement, cela montre simplement que la génération de données aléatoires pour les tests est une option viable.

mreggen
la source
16

Je suis en faveur des tests aléatoires et je les écris. Cependant, la question de savoir si elles sont appropriées dans un environnement de construction particulier et dans quelles suites de tests elles doivent être incluses est une question plus nuancée.

Exécutés localement (par exemple, pendant la nuit sur votre boîte de développement), des tests aléatoires ont trouvé des bogues à la fois évidents et obscurs. Les plus obscurs sont suffisamment obscurs pour que je pense que les tests aléatoires étaient vraiment les seuls réalistes pour les éliminer. En guise de test, j'ai pris un bogue difficile à trouver découvert via des tests aléatoires et j'ai demandé à une demi-douzaine de développeurs de crack d'examiner la fonction (environ une douzaine de lignes de code) là où elle s'est produite. Aucun n'a pu le détecter.

Beaucoup de vos arguments contre les données aléatoires sont des saveurs de "le test n'est pas reproductible". Cependant, un test randomisé bien écrit capturera la graine utilisée pour démarrer la graine aléatoire et la produira en cas d'échec. En plus de vous permettre de répéter le test à la main, cela vous permet de créer de manière triviale un nouveau test qui teste le problème spécifique en codant en dur la graine de ce test. Bien sûr, il est probablement plus agréable de coder manuellement un test explicite couvrant ce cas, mais la paresse a ses vertus, et cela vous permet même de générer automatiquement de nouveaux cas de test à partir d'une graine défaillante.

Le seul point que vous faites valoir que je ne peux pas débattre, cependant, c'est que cela brise les systèmes de construction. La plupart des tests de build et d'intégration continue s'attendent à ce que les tests fassent la même chose à chaque fois. Ainsi, un test qui échoue au hasard créera le chaos, échouera au hasard et pointera du doigt des changements qui étaient inoffensifs.

Une solution consiste donc à exécuter vos tests aléatoires dans le cadre des tests de construction et de CI, mais à les exécuter avec une valeur de départ fixe, pour un nombre fixe d'itérations . Par conséquent, le test fait toujours la même chose, mais explore toujours une partie de l'espace d'entrée (si vous l'exécutez pour plusieurs itérations).

Localement, par exemple, lorsque vous changez la classe concernée, vous êtes libre de l'exécuter pour plus d'itérations ou avec d'autres graines. Si les tests aléatoires deviennent de plus en plus populaires, vous pourriez même imaginer une suite spécifique de tests connus pour être aléatoires, qui pourraient être exécutés avec différentes graines (donc avec une couverture croissante au fil du temps), et où les échecs ne signifieraient pas la même chose en tant que systèmes de CI déterministes (c.-à-d., les exécutions ne sont pas associées 1: 1 aux changements de code et vous ne pointez donc pas du doigt un changement particulier lorsque les choses échouent).

Il y a beaucoup à dire sur les tests aléatoires, en particulier ceux bien écrits, alors ne soyez pas trop prompt à les rejeter!

BeeOnRope
la source
14

Si vous faites du TDD, je dirais que les données aléatoires sont une excellente approche. Si votre test est écrit avec des constantes, vous ne pouvez garantir que votre code fonctionne pour la valeur spécifique. Si votre test échoue de manière aléatoire sur le serveur de construction, il y a probablement un problème avec la façon dont le test a été écrit.

Des données aléatoires aideront à garantir que toute refactorisation future ne reposera pas sur une constante magique. Après tout, si vos tests sont votre documentation, la présence de constantes n'implique-t-elle pas qu'elle ne doit fonctionner que pour ces constantes?

J'exagère cependant je préfère injecter des données aléatoires dans mon test comme signe que "la valeur de cette variable ne devrait pas affecter le résultat de ce test".

Je dirai cependant que si vous utilisez une variable aléatoire, puis fourchez votre test en fonction de cette variable, alors c'est une odeur.

Jimmy Bosse
la source
10

Un avantage pour quelqu'un qui regarde les tests est que les données arbitraires ne sont clairement pas importantes. J'ai vu trop de tests qui impliquaient des dizaines d'éléments de données et il peut être difficile de dire ce qui doit être ainsi et ce qui se trouve être de cette façon. Par exemple, si une méthode de validation d'adresse est testée avec un code postal spécifique et que toutes les autres données sont aléatoires, vous pouvez être à peu près sûr que le code postal est la seule partie importante.

Trystan Spangler
la source
9
  • si c'est une valeur aléatoire et que le test échoue, nous devons a) réparer l'objet et b) nous forcer à tester cette valeur à chaque fois, donc nous savons que cela fonctionne, mais comme c'est aléatoire, nous ne savons pas quelle était la valeur

Si votre scénario de test n'enregistre pas avec précision ce qu'il teste, vous devez peut-être recoder le scénario de test. Je veux toujours avoir des journaux auxquels je peux me référer pour les cas de test afin de savoir exactement ce qui a causé l'échec, que ce soit en utilisant des données statiques ou aléatoires.

EBGreen
la source
9

Votre collègue fait des tests fuzz , bien qu'il ne le sache pas. Ils sont particulièrement utiles dans les systèmes de serveurs.

Robert Gould
la source
2
mais n'est-ce pas une chose fondamentalement différente des tests unitaires? et fait à un moment différent?
endolith
1
@endolith, aucune loi de la physique ne vous oblige à exécuter des tests particuliers à des moments particuliers
user253751
1
@immibis Mais il y a de bonnes raisons de faire des tests particuliers à des moments particuliers. Vous n'exécutez pas une batterie de tests unitaires chaque fois qu'un utilisateur clique sur un bouton "Ok".
endolith
5

Pouvez-vous générer des données aléatoires une fois (je veux dire exactement une fois, pas une fois par test), puis les utiliser dans tous les tests par la suite?

Je peux certainement voir l'intérêt de créer des données aléatoires pour tester les cas auxquels vous n'avez pas pensé, mais vous avez raison, avoir des tests unitaires qui peuvent réussir ou échouer au hasard est une mauvaise chose.

Programmeur hors-la-loi
la source
5

Vous devriez vous demander quel est le but de votre test.
Les tests unitaires consistent à vérifier la logique, le flux de code et les interactions d'objets. L'utilisation de valeurs aléatoires tente d'atteindre un objectif différent, ce qui réduit la concentration et la simplicité du test. Il est acceptable pour des raisons de lisibilité (génération d'UUID, d'identifiants, de clés, etc.).
Plus précisément pour les tests unitaires, je ne me souviens même pas une fois que cette méthode a réussi à trouver des problèmes. Mais j'ai vu de nombreux problèmes de déterminisme (dans les tests) en essayant d'être intelligent avec des valeurs aléatoires et principalement avec des dates aléatoires.
Le test Fuzz est une approche valide pour les tests d'intégration et les tests de bout en bout .

Ohad Bruker
la source
J'ajouterais que l'utilisation d'une entrée aléatoire pour le fuzzing est un mauvais substitut au fuzzing guidé par la couverture lorsque cela est possible.
gobenji
1

Si vous utilisez une entrée aléatoire pour vos tests, vous devez enregistrer les entrées afin de voir quelles sont les valeurs. De cette façon, si vous rencontrez un cas particulier, vous pouvez écrire le test pour le reproduire. J'ai entendu les mêmes raisons de la part de personnes pour ne pas utiliser d'entrée aléatoire, mais une fois que vous avez un aperçu des valeurs réelles utilisées pour un test particulier, ce n'est plus un problème.

La notion de données "arbitraires" est également très utile comme moyen de signifier quelque chose qui n'est pas important. Nous avons quelques tests d'acceptation qui nous viennent à l'esprit où il y a beaucoup de données de bruit qui ne sont pas pertinentes pour le test en cours.

Craigb
la source
0

En fonction de votre objet / application, les données aléatoires auraient une place dans les tests de charge. Je pense que le plus important serait d'utiliser des données qui testent explicitement les conditions aux limites des données.

EBGreen
la source
0

Nous venons de rencontrer cela aujourd'hui. Je voulais du pseudo-aléatoire (donc cela ressemblerait à des données audio compressées en termes de taille). J'AI TODO'd que je voulais aussi déterministe . rand () était différent sur OSX que sur Linux. Et à moins que je ne recommence, cela pourrait changer à tout moment. Nous l'avons donc changé pour qu'il soit déterministe mais toujours psuedo-aléatoire: le test est répétable, autant qu'en utilisant des données prédéfinies (mais plus commodément écrites).

Ce n'était PAS un test par une force brute aléatoire à travers des chemins de code. C'est la différence: toujours déterministe, toujours répétable, toujours en utilisant des données qui ressemblent à une entrée réelle pour exécuter un ensemble de vérifications intéressantes sur les cas limites dans une logique complexe. Encore des tests unitaires.

Est-ce que cela qualifie encore est aléatoire? Parlons de la bière. :-)

Tim James
la source
0

Je peux envisager trois solutions au problème des données de test:

  • Test avec des données fixes
  • Test avec des données aléatoires
  • Générez des données aléatoires une fois , puis utilisez-les comme données fixes

Je recommanderais de faire tout ce qui précède . Autrement dit, écrivez des tests unitaires répétables avec à la fois des cas extrêmes élaborés en utilisant votre cerveau et des données aléatoires que vous ne générez qu'une seule fois. Ensuite, écrivez un ensemble de tests aléatoires que vous exécutez également .

On ne devrait jamais s'attendre à ce que les tests aléatoires détectent quelque chose que vos tests répétables manquent. Vous devriez viser à tout couvrir avec des tests répétables et considérer les tests aléatoires comme un bonus. S'ils trouvent quelque chose, cela devrait être quelque chose que vous n'auriez pas pu raisonnablement prévoir; un vrai bizarre.

Tom W
la source
-2

Comment votre gars peut-il réexécuter le test alors qu'il n'a pas réussi à voir s'il l'a corrigé? C'est-à-dire qu'il perd la répétabilité des tests.

Bien que je pense qu'il est probablement utile de lancer une charge de données aléatoires lors des tests, comme mentionné dans d'autres réponses, cela relève plus de la rubrique des tests de charge qu'autre chose. C'est à peu près une pratique «test par espoir». Je pense qu'en réalité, votre gars ne pense tout simplement pas à ce qu'il essaie de tester, et compense ce manque de réflexion en espérant que le hasard finira par piéger une erreur mystérieuse.

Donc, l'argument que j'utiliserais avec lui est qu'il est paresseux. Ou, pour le dire autrement, s'il ne prend pas le temps de comprendre ce qu'il essaie de tester, cela montre probablement qu'il ne comprend pas vraiment le code qu'il écrit.

Greg Whitfield
la source
3
Il est possible d'enregistrer les données aléatoires ou la graine aléatoire afin que le test puisse être reproduit.
cbp