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?)
unit-testing
tdd
mocking
Adam V
la source
la source
Réponses:
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!
la source
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.
la source
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.
la source
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.
la source
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!
la source
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.
la source
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.
la source
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.
la source
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.
la source
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.
la source
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 .
la source
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.
la source
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.
la source
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. :-)
la source
Je peux envisager trois solutions au problème des données de test:
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.
la source
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.
la source