web-dev-qa-db-fra.com

Le code dupliqué est-il plus tolérable dans les tests unitaires?

Il y a quelque temps, j'ai ruiné plusieurs tests unitaires lorsque je les ai parcourus et les ai refactorisés pour les rendre plus SEC - l'intention de chaque test n'était plus claire. Il semble qu'il y ait un compromis entre la lisibilité et la maintenabilité des tests. Si je laisse du code dupliqué dans les tests unitaires, ils sont plus lisibles, mais si je change le SUT , je devrai traquer et changer chacun copie du code dupliqué.

Êtes-vous d'accord que ce compromis existe? Si oui, préférez-vous que vos tests soient lisibles ou maintenables?

110
Daryl Spitzer

Le code dupliqué est une odeur dans le code de test unitaire tout comme dans un autre code. Si vous avez dupliqué du code dans les tests, il est plus difficile de refactoriser le code d'implémentation car vous avez un nombre disproportionné de tests à mettre à jour. Les tests devraient vous aider à refactoriser en toute confiance, plutôt que d'être une lourde charge qui entrave votre travail sur le code testé.

Si la duplication est en cours d'installation, envisagez d'utiliser davantage la méthode setUp ou de fournir plus (ou plus de flexibilité) Méthodes de création .

Si la duplication est dans le code manipulant le SUT, alors demandez-vous pourquoi plusieurs tests dits "unitaires" exercent exactement la même fonctionnalité.

Si la duplication est dans les assertions, alors peut-être avez-vous besoin de Assertions personnalisées . Par exemple, si plusieurs tests ont une chaîne d'assertions comme:

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

Alors peut-être avez-vous besoin d'une seule méthode assertPersonEqual, pour pouvoir écrire assertPersonEqual(Person('Joe', 'Bloggs', 23), person). (Ou peut-être avez-vous simplement besoin de surcharger l'opérateur d'égalité sur Person.)

Comme vous le mentionnez, il est important que le code de test soit lisible. En particulier, il est important que l'intention d'un test soit claire. Je trouve que si de nombreux tests se ressemblent principalement (par exemple, les trois quarts des lignes sont identiques ou pratiquement identiques), il est difficile de repérer et de reconnaître les différences significatives sans les lire attentivement et les comparer. Je trouve donc que le refactoring pour supprimer la duplication aide à la lisibilité , car chaque ligne de chaque méthode de test est directement pertinente pour l'objectif du test. C'est beaucoup plus utile pour le lecteur qu'une combinaison aléatoire de lignes qui sont directement pertinentes et de lignes qui sont juste passe-partout.

Cela dit, les tests exercent parfois des situations complexes qui sont similaires mais toujours significativement différentes, et il est difficile de trouver un bon moyen de réduire la duplication. Faites preuve de bon sens: si vous pensez que les tests sont lisibles et que leur intention est claire, et que vous êtes à l'aise avec peut-être besoin de mettre à jour plus qu'un nombre théoriquement minimal de tests lors de la refactorisation du code invoqué par les tests, alors acceptez l'imperfection et déplacez sur quelque chose de plus productif. Vous pouvez toujours revenir et refactoriser les tests plus tard, lorsque l'inspiration frappe!

68
spiv

La lisibilité est plus importante pour les tests. Si un test échoue, vous voulez que le problème soit évident. Le développeur ne devrait pas avoir à parcourir un grand nombre de codes de test fortement factorisés pour déterminer exactement ce qui a échoué. Vous ne voulez pas que votre code de test devienne si complexe que vous devez écrire des tests unitaires.

Cependant, l'élimination de la duplication est généralement une bonne chose, tant qu'elle n'obscurcit rien, et l'élimination de la duplication dans vos tests peut conduire à une meilleure API. Assurez-vous simplement de ne pas dépasser le point de rendements décroissants.

178
Kristopher Johnson

Le code d'implémentation et les tests sont des animaux différents et les règles d'affacturage s'appliquent différemment à eux.

Le code ou la structure en double est toujours une odeur dans le code d'implémentation. Lorsque vous commencez à avoir du passe-partout dans l'implémentation, vous devez réviser vos abstractions.

En revanche, le test du code doit maintenir un niveau de duplication. La duplication dans le code de test atteint deux objectifs:

  • Garder les tests découplés. Un couplage de test excessif peut rendre difficile la modification d'un seul test défaillant qui doit être mis à jour car le contrat a changé.
  • Garder les tests significatifs dans l'isolement. Lorsqu'un seul test échoue, il doit être assez simple de savoir exactement ce qu'il teste.

J'ai tendance à ignorer la duplication triviale dans le code de test tant que chaque méthode de test reste inférieure à environ 20 lignes. J'aime quand le rythme setup-run-verify apparaît dans les méthodes de test.

Lorsque la duplication se glisse dans la partie "vérifier" des tests, il est souvent avantageux de définir des méthodes d'assertion personnalisées. Bien sûr, ces méthodes doivent toujours tester une relation clairement identifiée qui peut être rendue apparente dans le nom de la méthode: assertPegFitsInHole -> bon, assertPegIsGood -> mauvais.

Lorsque les méthodes de test deviennent longues et répétitives, je trouve parfois utile de définir des modèles de test à remplir qui prennent quelques paramètres. Ensuite, les méthodes de test réelles sont réduites à un appel à la méthode modèle avec les paramètres appropriés.

Quant à beaucoup de choses dans la programmation et les tests, il n'y a pas de réponse claire. Vous devez développer un goût, et la meilleure façon de le faire est de faire des erreurs.

44
ddaa

Vous pouvez réduire la répétition en utilisant plusieurs versions différentes de méthodes utilitaires de test .

Je suis plus tolérant à la répétition dans le code de test que dans le code de production, mais cela m'a parfois frustré. Lorsque vous changez la conception d'une classe et que vous devez revenir en arrière et modifier 10 méthodes de test différentes qui effectuent toutes les mêmes étapes de configuration, c'est frustrant.

8
Don Kirkby

Je suis d'accord. Le compromis existe mais est différent à différents endroits.

Je suis plus susceptible de refactoriser le code dupliqué pour la configuration de l'état. Mais moins susceptibles de refactoriser la partie du test qui exerce réellement le code. Cela dit, si l'exercice du code prend toujours plusieurs lignes de code, je pourrais penser que c'est une odeur et remanier le code réel sous test. Et cela améliorera la lisibilité et la maintenabilité du code et des tests.

7
stucampbell

Jay Fields a inventé la phrase que "les DSL devraient être DAMP, pas DRY", où HUMIDE signifie phrases descriptives et significatives. Je pense que la même chose s'applique également aux tests. Évidemment, trop de duplication est mauvaise. Mais supprimer à tout prix la duplication est encore pire. Les tests doivent agir comme des spécifications révélatrices d'intention. Si, par exemple, vous spécifiez la même fonction sous plusieurs angles différents, une certaine quantité de duplication est à prévoir.

6
Jörg W Mittag

Je pense que le code de test nécessite un niveau d'ingénierie similaire qui serait normalement appliqué au code de production. Il peut certainement y avoir des arguments en faveur de la lisibilité et je conviens que c'est important.

D'après mon expérience, cependant, je trouve que les tests bien factorisés sont plus faciles à lire et à comprendre. S'il y a 5 tests qui se ressemblent tous sauf une variable qui a été modifiée et l'assertion à la fin, il peut être très difficile de trouver ce qu'est cet élément différent. De même, s'il est factorisé de sorte que seule la variable qui change est visible et l'assertion, alors il est facile de comprendre ce que le test fait immédiatement.

Trouver le bon niveau d'abstraction lors des tests peut être difficile et je pense que cela en vaut la peine.

3
Kevin London

J'AIME rspec à cause de cela:

Il a 2 choses à aider -

  • groupes d'exemples partagés pour tester des comportements communs.
    vous pouvez définir un ensemble de tests, puis "inclure" cet ensemble dans vos vrais tests.

  • contextes imbriqués.
    vous pouvez essentiellement avoir une méthode de "configuration" et de "démontage" pour un sous-ensemble spécifique de vos tests, pas seulement pour tous dans la classe.

Le plus tôt que .NET/Java/d'autres frameworks de test adopteront ces méthodes, mieux ce sera (ou vous pourriez utiliser IronRuby ou JRuby pour écrire vos tests, ce qui, à mon avis, est la meilleure option)

3
Orion Edwards

"les refactoriser pour les rendre plus SECS - l'intention de chaque test n'était plus claire"

Il semble que vous ayez eu du mal à refactoriser. Je ne fais que deviner, mais si cela se révèle moins clair, cela ne signifie-t-il pas que vous avez encore du travail à faire pour que vous ayez des tests assez élégants qui sont parfaitement clairs?

C'est pourquoi les tests sont une sous-classe de UnitTest - vous pouvez donc concevoir de bonnes suites de tests correctes, faciles à valider et à effacer.

Dans le passé, nous avions des outils de test qui utilisaient différents langages de programmation. Il était difficile (ou impossible) de concevoir des tests agréables et faciles à travailler.

Vous avez toute la puissance de - quel que soit le langage que vous utilisez - Python, Java, C # - alors utilisez bien ce langage. Vous pouvez obtenir un code de test beau, clair et pas trop redondant. Il n'y a aucun compromis.

2
S.Lott

Je ne pense pas qu'il existe une relation entre un code plus dupliqué et plus lisible. Je pense que votre code de test devrait être aussi bon que votre autre code. Le code non répétitif est plus lisible que le code dupliqué lorsqu'il est bien fait.

2
Paco

Idéalement, les tests unitaires ne devraient pas beaucoup changer une fois qu'ils sont écrits, donc je pencherais pour la lisibilité.

Le fait que les tests unitaires soient aussi discrets que possible aide également à maintenir les tests concentrés sur les fonctionnalités spécifiques qu'ils ciblent.

Cela dit, j'ai tendance à réutiliser certains morceaux de code que je finis par utiliser encore et encore, comme le code de configuration qui est exactement le même à travers un ensemble de tests.

2
17 of 26