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?
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: cela 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 des if
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.
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 principalement 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:
Assume.that()
saute simplement le test des données si elle é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.Je ne suis pas de la mentalité stricte qu'il ne peut y avoir qu'une seule instruction assert
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 concerne les données, je suis d'avis d'utiliser les fonctionnalités de test pilotées par les données les 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.
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 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 des exemples, car le choix manuel des exemples pourrait manquer des cas Edge intéressants.
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-ce OK 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. Source
À mon avis, cela dépend de la condition du test.