Tester une liste… Tous dans le même test ou un test pour chaque condition?

21

Je teste qu'une fonction fait ce que l'on attend d'une liste. Je veux donc tester

f(null) -> null
f(empty) -> empty
f(list with one element) -> list with one element
f(list with 2+ elements) -> list with the same number of elements, doing what expected

Pour ce faire, quelle est la meilleure approche?

  • Test de tous les cas dans le même test (méthode), sous le nom "WorksAsExpected"
  • Placer un test pour chaque cas, ayant ainsi
    • "WorksAsExpectedWhenNull"
    • "WorksAsExpectedWhenEmpty"
    • "WorksAsExpectedWhenSingleElement"
    • "WorksAsExpectedWhenMoreElements"
  • Un autre choix auquel je ne pensais pas :-)
malarres
la source
2
Je les écrirais comme des cas de test distincts. Vous pouvez utiliser des tests paramétrés si votre système de test le prend en charge.
jonrsharpe
5
Si vous écrivez vos tests dans une donnée ... Quand ... Ensuite, le style devient alors évident qu'ils doivent vraiment être testés séparément ...
Robbie Dee
1
Je voudrais juste ajouter: IMO, il est bon de séparer les cas marginaux (comme null et vide) en tests séparés, car ceux-ci impliquent souvent une logique de cas particulier sur différentes implémentations possibles, et si ces tests échouent, ils indiqueront clairement dans de quelle manière le code sous test échoue (vous n'aurez pas à creuser plus profondément ou à déboguer le cas de test pour comprendre ce qui se passe).
Filip Milovanović
1
Liste avec des éléments en double?
atayenel du

Réponses:

30

La règle générale que j'utilise pour effectuer un ensemble de tests dans un ou plusieurs cas de test est la suivante: implique-t-il une seule configuration?

Donc, si je testais que, pour plusieurs éléments, il les a tous traités et a dérivé le résultat correct, je peux avoir deux assertions ou plus, mais je n'ai à configurer la liste qu'une seule fois. Donc, un cas de test est très bien.

Dans votre cas cependant, je devrais mettre en place une liste nulle, une liste vide, etc. C'est plusieurs configurations. Je créerais donc certainement plusieurs tests dans ce cas.

Comme d'autres l'ont mentionné, ces «tests multiples» pourraient exister en tant que scénario de test paramétré unique; c'est-à-dire que le même scénario de test est exécuté avec une variété de données de configuration. La clé pour savoir si cette solution est viable réside dans les autres parties du test: «action» et «affirmer». Si vous pouvez effectuer les mêmes actions et assertions sur chaque ensemble de données, utilisez cette approche. Si vous vous retrouvez à ajouter ifdes par exemple pour exécuter différents codes sur différentes parties de ces données, alors ce n'est pas la solution. Utilisez des cas de test individuels dans ce dernier cas.

David Arno
la source
14

Il y a un compromis. Plus vous emballerez dans un test, plus vous aurez probablement un effet oignon en essayant de le faire passer. En d'autres termes, le tout premier échec arrête ce test. Vous ne connaîtrez pas les autres assertions tant que vous n'aurez pas corrigé le premier échec. Cela dit, avoir un tas de tests unitaires qui sont généralement similaires, sauf pour le code de configuration, est un travail très chargé juste pour découvrir que certains travaux sont écrits et d'autres non.

Outils possibles, basés sur votre framework:

  • Théories . Une théorie vous permet de tester une série de faits sur un ensemble de données. Le framework alimentera ensuite vos tests avec plusieurs scénarios de données de test - soit par un champ, soit par une méthode statique qui génère les données. Si certains de vos faits s'appliquent sur la base de certaines conditions préalables et d'autres, ces cadres n'introduisent pas le concept d'une hypothèse . Votre Assume.that()saute simplement le test des données s'il échoue à la condition préalable. Cela vous permet de définir "Fonctionne comme prévu", puis d'alimenter simplement un grand nombre de données. Lorsque vous affichez les résultats, vous disposez d'une entrée pour les tests parents, puis d'une sous-entrée pour chaque élément de données.
  • Tests paramétrés . Les tests paramétrés étaient un précurseur des théories, donc il n'y a peut-être pas cette vérification des conditions que vous pouvez avoir avec les théories. Le résultat est le même. Vous vous affichez les résultats, vous avez une entrée parent pour le test lui-même, puis une entrée spécifique pour chaque point de données.
  • Un test avec plusieurs assertions . La configuration prend moins de temps, mais vous finissez par découvrir des problèmes petit à petit. Si le test est trop long et qu'il y a trop de scénarios différents testés, il y a deux gros risques: cela prendra trop de temps à exécuter, et votre équipe en aura assez et désactivera le test.
  • Tests multiples avec implémentation similaire . Il est important de noter que si les assertions sont différentes, les tests ne se chevauchent pas. Cependant, ce serait la sagesse conventionnelle d'une équipe axée sur le TDD.

Je ne suis pas de la mentalité stricte qu'il ne peut y avoir qu'une seule assertdéclaration dans votre test, mais je mets les restrictions que toutes les assertions devraient tester les post-conditions d'une seule action. Si la seule différence entre les tests réside dans les données, je suis d'accord pour utiliser les fonctionnalités de test pilotées par les données plus avancées comme les tests paramétrés ou les théories.

Évaluez vos options pour décider du meilleur résultat. Je dirai que "WorksAsExpectedWhenNull" est fondamentalement différent de tous les cas où vous avez affaire à une collection qui a un nombre variable d'éléments.

Berin Loritsch
la source
5

Ce sont des cas de test différents, mais le code du test est le même. L'utilisation de tests paramétrés est donc la meilleure solution. Si votre infrastructure de test ne prend pas en charge le paramétrage, extrayez le code partagé dans une fonction d'assistance et appelez-le à partir de cas de test individuels.

Essayez d'éviter la paramétrisation via une boucle dans un cas de test, car cela rend difficile la détermination de l'ensemble de données à l'origine de l'erreur.

Dans votre cycle TDD rouge – vert – refactor, vous devez ajouter un exemple d'ensemble de données à la fois. La combinaison de plusieurs cas de test en un test paramétré ferait partie de l'étape de refactorisation.

Une approche assez différente est le test de propriété . Vous devez créer divers tests (paramétrés) qui affirment diverses propriétés de votre fonction, sans spécifier de données d'entrée concrètes. Par exemple, une propriété pourrait être: pour toutes les listes xs, la liste ys = f(xs)a la même longueur que xs. Le cadre de test générerait alors des listes intéressantes et des listes aléatoires, et affirmerait que vos propriétés sont valables pour toutes. Cela s'éloigne de la spécification manuelle d'exemples, car le choix manuel d'exemples pourrait manquer des cas de bord intéressants.

amon
la source
Le "manque" dans la dernière phrase ne devrait-il pas être "trouver"?
Robbie Dee
@RobbieDee English est ambigu, fixe.
amon
3

Avoir un test pour chaque cas est approprié car tester un seul concept dans chaque test est une bonne ligne directrice qui est souvent recommandée.

Voir cet article: Est-il acceptable d'avoir plusieurs assertions dans un seul test unitaire? . Il y a également une discussion pertinente et détaillée:

Ma ligne directrice est généralement que vous testez 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é. Source - Roy Osherove

[...]

Les tests doivent échouer pour une seule raison, mais cela ne signifie pas toujours qu'il ne doit y avoir qu'une seule instruction Assert. À mon humble avis, il est plus important de conserver le modèle "Arrange, Act, Assert".

La clé est que vous n'avez qu'une seule action, puis vous inspectez les résultats de cette action à l'aide d'asserts. Mais c'est "Arrange, Act, Assert, End of test". Si vous êtes tenté de continuer les tests en effectuant une autre action et d'autres assertions par la suite, faites-en plutôt un test distinct. La source

fiston
la source
0

À mon avis, cela dépend de la condition du test.

  • Si votre test n'a qu'une seule condition pour configurer le test, mais de nombreux effets secondaires. plusieurs assertions sont acceptables.
  • Mais lorsque vous avez plusieurs conditions, cela signifie que vous avez plusieurs cas de test, chacun ne doit être couvert que par 1 test unitaire.
HungDL
la source
cela ressemble plus à un commentaire, voir Comment répondre
gnat