Dans le commentaire de cet excellent article , Roy Osherove a mentionné le projet OAPT conçu pour exécuter chaque affirmation dans un seul test.
Ce qui suit est écrit sur la page d'accueil du projet:
Les tests unitaires appropriés doivent échouer pour une seule raison. C'est pourquoi vous devriez utiliser un test d'assertion par unité.
Et aussi, Roy a écrit dans les commentaires:
Mon principe est généralement de tester un CONCEPT logique par test. vous pouvez avoir plusieurs assertions sur le même objet . ils seront généralement le même concept testé.
Je pense que, dans certains cas, plusieurs assertions sont nécessaires (par exemple , une assertion de garde ), mais en général, j'essaie d'éviter cela. Quel est ton opinion? Veuillez fournir un exemple concret dans lequel plusieurs assertions sont réellement nécessaires .
la source
RowTest
assertions ont été utilisées à la place de (MbUnit) /TestCase
(NUnit) pour tester divers comportements de cas marginaux. Utilisez les outils appropriés pour le travail! (Malheureusement, MSTest ne semble pas encore avoir la capacité de tester les lignes.)RowTest
etTestCase
utiliser des sources de données de test . J'utilise un simple fichier CSV avec un grand succès.Réponses:
Je ne pense pas que ce soit nécessairement une mauvaise chose , mais j'estime que nous devrions nous efforcer de n'avoir que des assertions simples dans nos tests. Cela signifie que vous écrivez beaucoup plus de tests et que nos tests ne testent qu'une chose à la fois.
Cela dit, je dirais que peut-être que la moitié de mes tests n’ont en fait qu’une seule affirmation. Je pense que cela ne devient une odeur de code (test?) Que lorsque vous avez environ cinq assertions ou plus dans votre test.
Comment résolvez-vous plusieurs assertions?
la source
Les tests doivent échouer pour une seule raison, mais cela ne signifie pas toujours qu'il ne devrait y avoir qu'une seule
Assert
déclaration. À mon humble avis, il est plus important de s'en tenir au modèle « organiser, agir, affirmer ».La clé est que vous n'avez qu'une seule action, puis vous inspectez les résultats de cette action en utilisant des assertions. Mais c'est "Organiser, Agir, Assert, Fin du test ". Si vous êtes tenté de continuer à effectuer des tests en effectuant une autre action et que d'autres affirmations sont effectuées par la suite, faites-en un test séparé.
Je suis heureux de voir plusieurs déclarations d'assertion qui font partie du test de la même action. par exemple
ou
Vous pouvez combiner ces éléments en une seule affirmation, mais ce n'est pas la même chose d'insister sur le fait que vous devriez ou devez . Il n'y a pas d'amélioration à les combiner.
Par exemple, le premier pourrait être
Mais ce n’est pas mieux - le message d’erreur est moins spécifique et n’a aucun autre avantage. Je suis sûr que vous pouvez penser à d’autres exemples dans lesquels la combinaison de deux ou trois affirmations (ou plus) en une seule condition booléenne rend plus difficile la lecture, l’altération et la difficulté de comprendre pourquoi elle a échoué. Pourquoi faire cela juste pour le plaisir d'une règle?
NB : Le code que j'écris ici est C # avec NUnit, mais les principes seront valables pour d'autres langages et frameworks. La syntaxe peut être très similaire aussi.
la source
Assert.IsBetween(10, 100, value)
, l'impressionExpected 8 to be between 10 and 100
vaut mieux que deux affirmations distinctes à mon avis. Vous pouvez certes affirmer que ce n'est pas nécessaire, mais il est généralement utile de se demander s'il est facile de le réduire à une seule affirmation avant d'en faire tout un ensemble.Je n'ai jamais pensé que plus d'une affirmation était une mauvaise chose.
Je le fais tout le temps:
Ici, j'utilise plusieurs assertions pour m'assurer que des conditions complexes peuvent être transformées en prédicat attendu.
Je ne teste qu'une unité (la
ToPredicate
méthode), mais je couvre tout ce à quoi je peux penser dans le test.la source
Lorsque j'utilise des tests unitaires pour valider un comportement de haut niveau, je mets absolument plusieurs assertions dans un seul test. Voici un test que j'utilise en réalité pour un code de notification d'urgence. Le code qui s'exécute avant le test met le système dans un état dans lequel, si le processeur principal est exécuté, une alarme est envoyée.
Il représente les conditions qui doivent exister à chaque étape du processus pour que je sois confiant que le code se comporte comme prévu. Si une seule assertion échoue, peu m'importe que les assertions restantes ne soient même pas exécutées; étant donné que l'état du système n'est plus valide, ces affirmations ultérieures ne me diraient rien de précieux. * En cas d'
assertAllUnitsAlerting()
échec, je ne saurais pas quoi faire duassertAllNotificationSent()
succès OU échec jusqu'à ce que je détermine la cause de l'erreur précédente. et corrigé.(* - D'accord, ils pourraient éventuellement être utiles pour résoudre le problème. Mais l'information la plus importante, à savoir que le test a échoué, a déjà été reçue.)
la source
Une autre raison pour laquelle je pense que plusieurs assertions dans une méthode n'est pas une mauvaise chose est décrite dans le code suivant:
Dans mon test, je veux simplement tester que
service.process()
renvoie le nombre correct dansInner
les instances de classe.Au lieu de tester ...
Je fais
la source
Assert.notNull
s sont redondants, votre test échouera avec un NPE s’ils sont nuls.if
) passera sires
isnull
Assert.assertEquals(..., service.process().getInner());
, possible avec des variables extraites si la ligne devient "trop longue"Je pense qu'il y a beaucoup de cas où écrire plusieurs assertions est valide dans la règle qu'un test ne doit échouer que pour une seule raison.
Par exemple, imaginez une fonction qui analyse une chaîne de date:
Si le test échoue, c'est pour une raison unique, l'analyse est incorrecte. Si vous estimez que ce test peut échouer pour trois raisons différentes, votre définition de "une seule raison" serait trop fine.
la source
Je ne connais aucune situation dans laquelle il serait judicieux d'insérer plusieurs assertions dans la méthode [Test] elle-même. La raison principale pour laquelle les gens aiment avoir plusieurs assertions est qu'ils essaient d'avoir une classe [TestFixture] pour chaque classe testée. Au lieu de cela, vous pouvez diviser vos tests en plusieurs classes [TestFixture]. Cela vous permet de voir de différentes manières que le code n'a peut-être pas réagi comme prévu, au lieu de celui où la première assertion a échoué. Pour ce faire, vous devez tester au moins un répertoire par classe contenant de nombreuses classes [TestFixture]. Chaque classe [TestFixture] serait nommée d'après l'état spécifique d'un objet que vous allez tester. La méthode [SetUp] mettra l'objet dans l'état décrit par le nom de la classe. Ensuite, vous avez plusieurs méthodes [Test], chacune affirmant différentes choses que vous vous attendriez à être vraies, compte tenu de l'état actuel de l'objet. Chaque méthode [Test] porte le nom de la chose qu’elle est en train d’affirmer, sauf peut-être le nom du concept plutôt que simplement une lecture anglaise du code. Ensuite, chaque implémentation de méthode [Test] n'a besoin que d'une seule ligne de code dans laquelle elle affirme quelque chose. Un autre avantage de cette approche est que cela rend les tests très lisibles car il devient très clair ce que vous testez et ce que vous attendez en regardant simplement les noms de classe et de méthode. Cela s’améliorera également au fur et à mesure que vous commencerez à comprendre tous les petits cas que vous voulez tester et à trouver des bogues. sauf peut-être qu'il pourrait être nommé d'après le concept au lieu d'une lecture en anglais du code. Ensuite, chaque implémentation de méthode [Test] n'a besoin que d'une seule ligne de code dans laquelle elle affirme quelque chose. Un autre avantage de cette approche est que cela rend les tests très lisibles car il devient très clair ce que vous testez et ce que vous attendez en regardant simplement les noms de classe et de méthode. Cela s’améliorera également au fur et à mesure que vous commencerez à comprendre tous les petits cas que vous voulez tester et à trouver des bogues. sauf peut-être qu'il pourrait être nommé d'après le concept au lieu d'une lecture en anglais du code. Ensuite, chaque implémentation de méthode [Test] n'a besoin que d'une seule ligne de code dans laquelle elle affirme quelque chose. Un autre avantage de cette approche est que cela rend les tests très lisibles car il devient très clair ce que vous testez et ce que vous attendez en regardant simplement les noms de classe et de méthode. Cela s’améliorera également au fur et à mesure que vous commencerez à comprendre tous les petits cas que vous voulez tester et à trouver des bogues. et ce que vous attendez simplement en regardant les noms de classe et de méthode. Cela s’améliorera également au fur et à mesure que vous commencerez à comprendre tous les petits cas que vous voulez tester et à trouver des bogues. et ce que vous attendez simplement en regardant les noms de classe et de méthode. Cela s’améliorera également au fur et à mesure que vous commencerez à comprendre tous les petits cas que vous voulez tester et à trouver des bogues.
Cela signifie généralement que la dernière ligne de code à l'intérieur de la méthode [SetUp] doit stocker une valeur de propriété ou une valeur de retour dans une variable d'instance privée de [TestFixture]. Vous pouvez ensuite affirmer plusieurs choses différentes à propos de cette variable d'instance à partir de différentes méthodes [Test]. Vous pouvez également faire des assertions sur les propriétés différentes de l'objet à tester, à présent qu'il se trouve dans l'état souhaité.
Parfois, vous devez faire des assertions en cours de route au fur et à mesure que l'objet à tester est dans l'état souhaité, afin de vous assurer de ne pas vous perdre avant de le placer dans l'état souhaité. Dans ce cas, ces assertions supplémentaires doivent apparaître dans la méthode [SetUp]. Si quelque chose ne va pas dans la méthode [SetUp], il sera clair que quelque chose n'allait pas avec le test avant que l'objet n'atteigne l'état souhaité que vous vouliez tester.
Un autre problème que vous pouvez rencontrer est peut-être que vous testez une exception que vous vous attendiez à recevoir. Cela peut vous inciter à ne pas suivre le modèle ci-dessus. Cependant, vous pouvez toujours y parvenir en attrapant l'exception à l'intérieur de la méthode [SetUp] et en la stockant dans une variable d'instance. Cela vous permettra d'affirmer différentes choses à propos de l'exception, chacune dans sa propre méthode [Test]. Vous pouvez ensuite également affirmer d'autres choses à propos de l'objet testé pour vous assurer qu'il n'y a aucun effet secondaire non souhaité de la part de l'exception levée.
Exemple (cela serait divisé en plusieurs fichiers):
la source
[Test]
méthode, cette classe est ré-instanciée et la[SetUp]
méthode est exécutée à nouveau. Cela élimine le .NET Garbage Collector et ralentit considérablement les tests: plus de 5 minutes localement, plus de 20 minutes sur le serveur de génération. Les tests de 20K devraient durer environ 2 à 3 minutes. Je ne recommanderais pas ce style de test, en particulier pour une suite de tests de grande taille.Avoir plusieurs assertions dans le même test n'est un problème que lorsque le test échoue. Ensuite, vous devrez peut-être déboguer le test ou analyser l'exception pour déterminer l'assertion qui échoue. Avec une affirmation dans chaque test, il est généralement plus facile de déterminer ce qui ne va pas.
Je ne peux pas penser à un scénario dans lequel plusieurs assertions sont réellement nécessaires , car vous pouvez toujours les réécrire en tant que conditions multiples dans la même assertion. Cela peut toutefois être préférable si, par exemple, vous avez plusieurs étapes pour vérifier les données intermédiaires entre les étapes plutôt que de risquer que les étapes ultérieures se bloquent en raison d'une mauvaise entrée.
la source
Si votre test échoue, vous ne saurez pas si les affirmations suivantes se briseront également. Cela signifie souvent que vous manquerez d'informations précieuses pour déterminer la source du problème. Ma solution consiste à utiliser une assertion mais avec plusieurs valeurs:
Cela me permet de voir toutes les assertions échouées à la fois. J'utilise plusieurs lignes, car la plupart des IDE affichent côte à côte les différences de chaînes dans une boîte de dialogue de comparaison.
la source
Si vous avez plusieurs assertions dans une même fonction de test, je m'attends à ce qu'elles soient directement pertinentes pour le test que vous effectuez. Par exemple,
Avoir beaucoup de tests (même si vous estimez que c'est probablement un excès) n'est pas une mauvaise chose. Vous pouvez soutenir que les tests essentiels et essentiels sont plus importants. Ainsi, lorsque vous effectuez une assertion, assurez-vous que vos instructions d'assertion sont correctement placées plutôt que de vous inquiéter de trop d'assertions multiples. Si vous en avez besoin de plusieurs, utilisez-en plusieurs.
la source
L'objectif du test unitaire est de vous fournir le plus d'informations possible sur ce qui ne fonctionne pas, mais également de vous aider à identifier avec précision les problèmes les plus fondamentaux en premier. Lorsque vous savez logiquement qu'une assertion échouera en raison de l'échec d'une autre assertion ou, en d'autres termes, qu'il existe une relation de dépendance entre les tests, il est alors logique de les convertir en assertions multiples au sein d'un même test. Cela présente l'avantage de ne pas ajouter aux résultats des tests des erreurs évidentes qui auraient pu être éliminées si nous avions renoncé lors de la première assertion dans un seul test. Dans le cas où cette relation n'existe pas, la préférence serait naturellement de séparer ces assertions en tests individuels car sinon, la découverte de ces échecs nécessiterait plusieurs itérations de tests pour résoudre tous les problèmes.
Si vous concevez également les unités / classes de telle sorte que des tests trop complexes doivent être écrits, cela allégera le fardeau lors des tests et favorisera probablement une meilleure conception.
la source
Oui, il est correct d’avoir plusieurs assertions tant qu’un test en échec vous fournit suffisamment d’informations pour pouvoir diagnostiquer l’échec. Cela dépendra de ce que vous testez et des modes de défaillance.
Je n'ai jamais trouvé que de telles formulations étaient utiles (une classe devrait avoir une raison de changer, c'est un exemple d'adage aussi inutile). Considérons une assertion que deux chaînes sont égales, ceci équivaut sémantiquement à affirmer que la longueur des deux chaînes est la même et que chaque caractère à l’index correspondant est égal.
Nous pourrions généraliser et dire que tout système de plusieurs assertions pourrait être réécrit en une seule assertion, et que toute assertion unique pourrait être décomposée en un ensemble d'assertions plus petites.
Alors, concentrez-vous uniquement sur la clarté du code et des résultats du test, et laissez-le guider le nombre d'assertions que vous utilisez plutôt que l'inverse.
la source
La réponse est très simple: si vous testez une fonction qui modifie plus d'un attribut, du même objet ou même deux objets différents, et que la correction de la fonction dépend des résultats de toutes ces modifications, vous souhaitez affirmer que chacun de ces changements a été correctement effectué!
Je conçois l'idée d'un concept logique, mais la conclusion inverse dirait qu'aucune fonction ne doit jamais changer plus d'un objet. Mais c'est impossible à mettre en œuvre dans tous les cas, selon mon expérience.
Prenons le concept logique d’une transaction bancaire: retirer un montant d’un compte bancaire DOIT dans la plupart des cas inclure l’ajout de ce montant à un autre compte. Vous ne voulez JAMAIS séparer ces deux choses, elles forment une unité atomique. Vous voudrez peut-être créer deux fonctions (retire / addMoney) et écrire ainsi deux tests unitaires différents - en plus. Mais ces deux actions doivent avoir lieu dans une transaction et vous voulez également vous assurer que la transaction fonctionne. Dans ce cas, il ne suffit tout simplement pas de garantir le succès des étapes individuelles. Vous devez vérifier les deux comptes bancaires, dans votre test.
Il peut y avoir des exemples plus complexes que vous ne testeriez pas dans un test unitaire, mais dans un test d'intégration ou d'acceptation. Mais ces limites sont fluides, à mon humble avis! Ce n'est pas si facile de décider, c'est une question de circonstances et peut-être de préférence personnelle. Retirer de l’argent de l’un d’eux et l’ajouter à un autre compte reste une fonction très simple et certainement un candidat pour les tests unitaires.
la source
Cette question est liée au problème classique de l’équilibre entre les problèmes de code de spaghetti et de lasagne.
Si vous avez plusieurs assertions, vous pouvez facilement résoudre le problème des spaghettis sans avoir la moindre idée de la nature du test. Cependant, si vous ne soumettez qu'une seule affirmation par test, vos tests sont tout aussi illisibles que si vous avez plusieurs tests dans une grosse lasagne. .
Il y a quelques exceptions, mais dans ce cas, garder le pendule au milieu est la solution.
la source
Je ne suis même pas d'accord avec le "échec pour une seule raison" en général. Ce qui est plus important, c’est que les tests sont courts et se lisent clairement en imo.
Cela n’est cependant pas toujours réalisable et quand un test est compliqué, un nom descriptif (long) et tester moins de choses ont plus de sens.
la source