Si votre code de test unitaire «sent», est-ce vraiment important?

52

Habituellement, je jette mes tests unitaires ensemble en utilisant copier / coller et toutes sortes d’autres mauvaises pratiques. Les tests unitaires finissent généralement par être très laids, ils sont pleins de "code odor", mais est-ce vraiment important? Je me dis toujours tant que le "vrai" code est "bon", c'est tout ce qui compte. De plus, les tests unitaires requièrent généralement diverses «manipulations malodorantes», telles que les fonctions de substitution.

Dans quelle mesure devrais-je être préoccupé par des tests unitaires mal conçus ("malodorants")?

Boutons840
la source
8
Comment peut-il être difficile de corriger le code que vous copiez toujours?
JeffO
7
L'odeur de code dans les tests pourrait facilement être un indicateur d'odeur cachée dans le reste du code. Pourquoi aborder les tests comme s'ils n'étaient pas du "vrai" code?
HorusKol

Réponses:

75

Le test unitaire sent-il important? Oui définitivement. Cependant, elles diffèrent des odeurs de code car les tests unitaires ont un objectif différent et ont des tensions différentes qui informent leur conception. De nombreuses odeurs dans le code ne s'appliquent pas aux tests. Compte tenu de ma mentalité TDD, je dirais même que les odeurs des tests unitaires sont plus importantes que les odeurs de code, car le code sert uniquement à satisfaire les tests.

Voici quelques odeurs de tests unitaires courantes:

  • Fragilité : vos tests échouent-ils souvent et de manière inattendue, même pour des modifications de code apparemment triviales ou sans rapport?
  • Fuite d'état : vos tests échouent-ils différemment en fonction, par exemple, de l'ordre dans lequel ils sont exécutés?
  • Installation / Démontage Bloat : Vos blocs d'installation / de démontage sont-ils longs et longs? Est-ce qu'ils effectuent une sorte de logique métier?
  • Durée d'exécution lente : Vos tests prennent-ils beaucoup de temps? Est-ce que l'un de vos tests unitaires prend plus d'un dixième de seconde? (Oui, je suis sérieux, un dixième de seconde.)
  • Friction : Les tests existants rendent-ils difficile la rédaction de nouveaux tests? Vous trouvez-vous souvent aux prises avec des échecs lors de la refactorisation?

L’importance des odeurs réside dans le fait qu’elles sont des indicateurs utiles de la conception ou d’autres questions plus fondamentales, à savoir "là où il y a de la fumée, il y a un incendie". Ne vous contentez pas de rechercher des odeurs de test, recherchez également leur cause sous-jacente.

Voici par contre quelques bonnes pratiques pour les tests unitaires:

  • Retour rapide et ciblé : Vos tests doivent permettre d’isoler rapidement l’échec et de vous fournir des informations utiles quant à sa cause.
  • Minimiser la distance du code de test : il doit exister un chemin clair et court entre le test et le code qui le met en œuvre. Les longues distances créent des boucles de rétroaction inutilement longues.
  • Testez une chose à la fois : les tests unitaires ne devraient tester qu'une seule chose. Si vous avez besoin de tester autre chose, écrivez un autre test.
  • Un bogue est un test que vous avez oublié d'écrire : Que pouvez-vous apprendre de cette incapacité à écrire de meilleurs tests plus complets à l'avenir?
Rein Henrichs
la source
2
"Les tests unitaires ont un but différent et ont un ensemble de tensions différent qui en informe la conception". Par exemple, ils devraient DAMP, pas nécessairement DRY.
Jörg W Mittag
3
@ Jörg D'accord, mais DAMP représente-t-il réellement quelque chose? : D
Rein Henrichs
5
@Rein: Phrases descriptives et significatives. En outre, pas complètement sec. Voir codeshelter.wordpress.com/2011/04/07/…
Robert Harvey
+1 Je ne connaissais pas non plus la signification de DAMP.
egarcia
2
@ironcode Si vous ne pouvez pas travailler sur un système de manière isolée sans craindre de briser son intégration avec d'autres systèmes, cela sent le couplage étroit entre moi et moi. C’est précisément le but de l’identification des odeurs de test comme une longue durée: elles vous informent des problèmes de conception tels que le couplage étroit. La réponse ne devrait pas être "Oh, alors cette odeur de test n'est pas valide", elle devrait être "Qu'est-ce que cette odeur me dit à propos de ma création?" Les tests unitaires spécifiant l'interface externe de votre système doivent suffire à vous indiquer si vos modifications interrompent ou non l'intégration avec ses consommateurs.
Rein Henrichs
67

Être concerné. Vous écrivez des tests unitaires pour prouver que votre code agit comme vous le souhaitez. Ils vous permettent de refactorer rapidement avec confiance. Si vos tests sont fragiles, difficiles à comprendre ou difficiles à gérer, vous ignorerez ou désactiverez les tests à mesure que votre base de code évoluera, annulant ainsi de nombreux avantages de l'écriture de tests.

Jayraynet
la source
17
+1 Au moment où vous commencez à ignorer les tests qui échouent, ils n'ajoutent aucune valeur.
19

Je viens juste de finir de lire The Art of Unit Testing il y a quelques jours. L'auteur recommande de mettre autant de soin dans vos tests unitaires que dans votre code de production.

J'ai moi-même fait l'expérience de tests mal écrits et impossibles à maintenir. J'ai écrit certains des miens. Il est pratiquement garanti que si un test est difficile à maintenir, il ne sera pas maintenu. Une fois que les tests ne sont plus synchronisés avec le code testé, ils sont devenus des nids de mensonges et de tromperies. Le but des tests unitaires est d’instiller la confiance que nous n’avons rien cassé (c’est-à-dire qu’ils créent la confiance). Si les tests ne peuvent pas faire confiance, ils sont pires qu'inutiles.

Joshua Smith
la source
4
+1 vous devez maintenir les tests, tout comme le code de production
Hamish Smith
Un autre très bon livre sur ce sujet est "Patterns de test xUnit - Code de test Refatoring" de Gerard Meszaros.
adrianboimvaser
7

Tant que votre test unitaire teste réellement votre code dans "la plupart" des cas. (Dit le plus volontairement, car il est parfois difficile de trouver tous les résultats possibles). Je pense que le code "malodorant" est votre préférence personnelle. J'écris toujours le code de manière à ce que je puisse le lire et le comprendre en quelques secondes, plutôt que de fouiller dans les ordures et d'essayer de comprendre ce qui est quoi. Surtout quand on y revient après un temps considérable.

En bout de ligne - ses tests, il suppose être facile. Vous ne voulez pas vous confondre avec le code "malodorant".

Luke
la source
5
+1: Ne vous occupez pas du code de test unitaire. Certaines des questions les plus stupides concernent le fait de rendre le code de test unitaire plus "robuste", de sorte qu'un changement de programmation ne rompt pas le test unitaire. Niaiserie. Les tests unitaires sont conçus pour être fragiles. Ce n'est pas grave s'ils sont moins robustes que le code d'application qu'ils sont conçus pour tester.
S.Lott
1
@ S.Lott Les deux sont d'accord et pas d'accord, pour des raisons mieux expliquées dans ma réponse.
Rein Henrichs
1
@ Rein Henrichs: ce sont des odeurs bien plus graves que celles décrites dans la question. "copier et coller" et "sembler plutôt laid" ne semblent pas aussi mauvais que les odeurs que vous décrivez lorsque les tests ne sont pas fiables.
S.Lott
1
@ S.Lott exactement, je pense que si nous allons parler de "tests unitaires", il est crucial de faire cette distinction. Les odeurs de code dans les tests unitaires ne le sont souvent pas. Ils sont écrits à des fins différentes et les tensions qui les rendent nauséabondes dans un contexte sont très différents dans l’autre.
Rein Henrichs
2
@ S.Lott btw, cela semble être une occasion idéale d’utiliser la fonctionnalité de discussion en ligne très négligée :)
Rein Henrichs
6

Absolument. Certaines personnes disent "n'importe quel test vaut mieux que pas de test du tout". Je suis tout à fait en désaccord - des tests mal écrits réduisent votre temps de développement, et vous finissez par perdre des jours à réparer des tests «cassés» car ils ne sont pas de bons tests unitaires. Pour moi en ce moment, les deux choses sur lesquelles je me concentre pour rendre mes tests utiles, plutôt que comme un fardeau, sont:

Maintenabilité

Vous devriez tester le résultat ( ce qui se passe), pas la méthode ( comment cela se produit). Votre configuration pour le test doit être aussi découplée que possible de la mise en œuvre: configurez uniquement les résultats pour les appels de service, etc., qui sont absolument nécessaires.

  • Utilisez un cadre moqueur pour vous assurer que vos tests ne dépendent d'aucun facteur externe
  • Privilégiez les moignons (si votre cadre les distingue) autant que possible
  • Aucune logique dans les tests! Les ifs, les switchs, les for-onees, les cases, les try-catch, etc.

Lisibilité

Vous pouvez autoriser un peu plus de répétition dans vos tests, ce que vous ne pouvez normalement pas autoriser dans votre code de production, si cela les rend plus lisibles. Juste équilibrer cela avec les choses de maintenabilité ci-dessus. Soyez explicite dans ce que le test fait!

  • Essayez de conserver un style "arrangez-vous, agissez, affirmez" pour vos tests. Cela sépare votre configuration et vos attentes du scénario, de l'action en cours d'exécution et du résultat affirmé.
  • Conservez une assertion logique par test (si le nom de votre test contient "et", vous devrez peut-être le scinder en plusieurs tests).

En conclusion, vous devriez être très préoccupé par les tests "malodorants" - ils peuvent ne constituer qu'une perte de temps, sans valeur.

Vous avez dit:

Les tests unitaires requièrent généralement diverses "manipulations malodorantes", telles que les fonctions de substitution.

Il semblerait que vous ayez le droit de lire certaines techniques de tests unitaires, telles que l’utilisation d’un framework Mocking, pour vous rendre la vie beaucoup plus facile. Je recommanderais très fortement The Art of Unit Testing , qui couvre ce qui précède et bien plus encore. J'ai trouvé cela enrichissant après avoir lutté pendant longtemps avec des tests mal écrits, impossibles à maintenir, "malodorants". C'est l'un des meilleurs investissements en termes de temps que j'ai faits cette année!

Mjhilton
la source
Excellente réponse. Je n'ai qu'un petit petit reproche: une action par test est bien plus importante qu'une "assertion logique par test". Le fait est que peu importe le nombre d'étapes nécessaires pour organiser un test, il ne devrait y avoir qu'une seule "action de code de production sous test". Si cette action devait avoir plus d'un effet secondaire, vous pourriez avoir plusieurs assertions.
Désillusionné
5

Deux questions pour vous:

  • Etes-vous absolument convaincu que vous testez ce que vous pensez tester?
  • Si quelqu'un d'autre examine le test unitaire, sera-t-il capable de comprendre ce que le code est censé faire ?

Dans vos tests unitaires, il existe des moyens de gérer des tâches répétitives qui consistent le plus souvent en un code d'installation et de démontage. Vous avez essentiellement une méthode de configuration de test et une méthode de démontage de test - tous les frameworks de test unitaire prennent en charge cela.

Les tests unitaires doivent être petits et faciles à comprendre. Si ce n'est pas le cas et que le test échoue, comment allez-vous résoudre le problème dans un délai relativement court? Facilitez-vous la vie pendant quelques mois lorsque vous devez revenir au code.

Berin Loritsch
la source
5

Quand la corbeille commence à sentir, il est temps de la sortir. Votre code de test doit être aussi propre que votre code de production. Souhaitez-vous le montrer à votre mère?

Bill Leeper
la source
3

En plus des autres réponses ici.

Un code de mauvaise qualité dans les tests unitaires n'est pas limité à votre suite de tests unitaires.

L'un des rôles remplis par les tests unitaires est la documentation.

La suite de tests unitaires est l’un des endroits où l’on cherche à savoir comment une API doit être utilisée.

Les appelants de votre API ne sont pas susceptibles de copier des parties de votre suite de tests unitaires, votre code de suite de tests défectueux pouvant ainsi infecter du code réel ailleurs.

Paul Butcher
la source
3

J'ai failli ne pas soumettre ma réponse car il m'a fallu un certain temps pour comprendre comment aborder cette question comme une question légitime.

Les raisons pour lesquelles les programmeurs adoptent des "meilleures pratiques" ne le sont pas pour des raisons esthétiques ou parce qu'elles veulent un code "parfait". C'est parce que cela leur fait gagner du temps.

  • Cela permet de gagner du temps, car un bon code est plus facile à lire et à comprendre.
  • Cela fait gagner du temps, car lorsque vous avez besoin de corriger un bogue, il est plus facile et plus rapide de le localiser.
  • Cela vous fait gagner du temps car lorsque vous souhaitez étendre le code, il est plus simple et plus rapide de le faire.

Donc, la question que vous posez (de mon point de vue) est: dois-je gagner du temps ou écrire un code qui me fera perdre du temps?

A cette question, je peux seulement dire, quelle importance a votre temps?

Pour votre information: le stubbing, les moqueries et les corrections de singe ont toutes des utilisations légitimes. Ils ne "sentent" que lorsque leur utilisation n'est pas appropriée.

dietbuddha
la source
2

J'essaie spécifiquement de ne pas faire mes tests unitaires trop robusts. J'ai vu des tests unitaires commencer à gérer les erreurs sous prétexte d'être robustes. Vous vous retrouvez avec des tests qui avalent les insectes qu’ils essaient de détecter. Les tests unitaires doivent également faire des choses géniales assez souvent pour que les choses fonctionnent. Réfléchissez à l'idée même que les accesseurs privés utilisent la réflexion… si je voyais un groupe de ces derniers dans le code de production, je serais inquiet 9 fois sur 10. Je pense qu'il faudrait plus de temps pour réfléchir à ce qui est testé. , plutôt que la propreté du code. De toute façon, les tests devront être changés assez fréquemment si vous effectuez une refactorisation majeure, alors pourquoi ne pas simplement les pirater ensemble, avoir un peu moins de sentiments de propriété et être plus motivés pour les réécrire ou les retravailler le moment venu?

Morgan Herlocker
la source
2

Si vous optez pour une couverture lourde et de bas niveau, vous passerez autant de temps, voire plus, dans le code de test que dans le code de produit lorsque vous apportez des modifications sérieuses.

En fonction de la complexité de la configuration du test, le code peut être plus complexe. (httpcontext.current vs l'horrible monstre d'essayer de construire un faux avec précision, par exemple)

À moins que votre produit ne modifie rarement les interfaces existantes et que vos entrées au niveau de l'unité soient très simples à configurer, je serais au moins inquiet quant à la compréhension des tests en tant que produit réel.

Facture
la source
0

Code Les odeurs ne sont qu'un problème dans le code qui impose de les modifier ou de les comprendre à un moment donné.

Je pense donc que vous devriez corriger les odeurs de code lorsque vous devez vous "rapprocher" du test unitaire donné, mais pas avant.

Ian
la source