Est-il normal de consacrer autant, sinon plus, de temps à la rédaction de tests que le code réel?

211

Je trouve les tests beaucoup plus difficiles et plus difficiles à écrire que le code réel qu'ils testent. Il n'est pas inhabituel que je passe plus de temps à écrire le test que le code qu'il teste.

Est-ce normal ou est-ce que je fais quelque chose de mal?

Les questions «Les tests unitaires ou le développement piloté par les tests en valent-ils la peine? ”,“ Nous passons plus de temps à mettre en place un test fonctionnel qu’à mettre en place le système lui-même, est-ce normal? ”Et leurs réponses sont plus axées sur la question de savoir si le test en vaut la peine (comme dans“ devons-nous ignorer complètement les tests? ”). Bien que je sois convaincu que les tests sont importants, je me demande si je passe plus de temps à faire des tests que du code réel ou si ce n’est que moi.

À en juger par le nombre de points de vue, de réponses et de critiques positives que ma question a été reçue, je ne peux que supposer que c’est une préoccupation légitime qui n’est abordée dans aucune autre question du site Web.

chargé de printemps
la source
20
Anecdotique, mais je trouve que je passe à peu près autant de temps à écrire des tests qu’à écrire du code lorsque je les utilise. C'est quand je me trompe et que j'écris des tests après le fait que je passe plus de temps sur les tests que sur le code.
RubberDuck
10
Vous passez également plus de temps à lire qu'à écrire votre code.
Thorbjørn Ravn Andersen
27
En outre, les tests sont du code réel. Vous n’envoyez tout simplement pas cette partie aux clients.
Thorbjørn Ravn Andersen
5
Idéalement, vous passerez également plus de temps à exécuter qu'à écrire votre code. (Sinon, vous feriez simplement la tâche à la main.)
Joshua Taylor
5
@RubberDuck: Expérience opposée ici. Parfois, lorsque j'écris des tests après coup, le code et la conception sont déjà bien rangés, je n'ai donc pas besoin de trop récrire le code et les tests. Il faut donc moins de temps pour écrire les tests. Ce n'est pas une règle, mais cela m'arrive assez souvent.
Giorgio

Réponses:

205

Je me souviens d'un cours de génie logiciel, que l'un consacrait environ 10% de son temps de développement à l'écriture de nouveau code et l'autre 90% au débogage, aux tests et à la documentation.

Étant donné que les tests unitaires capturent le débogage et les efforts de test dans du code (potentiellement automatisable), il serait logique que davantage d'efforts y soient consacrés; le temps réellement nécessaire ne devrait pas être beaucoup plus que le débogage et les tests que l’on ferait sans écrire les tests.

Enfin, les tests devraient également servir de documentation! On devrait écrire des tests unitaires de la manière dont le code est destiné à être utilisé; c'est-à-dire que les tests (et leur utilisation) doivent être simples, mettez les éléments compliqués dans la mise en œuvre.

Si vos tests sont difficiles à écrire, le code qu'ils testent est probablement difficile à utiliser!

esoterik
la source
4
C'est peut-être un bon moment pour comprendre pourquoi le code est si difficile à tester :) Essayez de "dérouler" des lignes multifonctions complexes qui ne sont pas strictement nécessaires, comme des opérateurs binaires / ternaires massivement imbriqués ... Je déteste vraiment les fichiers binaires inutiles. / Opérateur ternaire qui a également un opérateur binaire / ternaire comme l'un des chemins ...
Nelson
53
Je ne suis pas d'accord avec la dernière partie. Si vous visez une couverture de tests unitaires très élevée, vous devez couvrir les cas d'utilisation rares et parfois totalement opposés à l'utilisation prévue de votre code. La rédaction de tests pour ces cas critiques pourrait bien être la partie la plus fastidieuse de la tâche.
otto
Je l'ai dit ailleurs, mais les tests unitaires ont tendance à durer beaucoup plus longtemps, car la plupart du code a tendance à suivre une sorte de "principe de Pareto": vous pouvez couvrir environ 80% de votre logique avec environ 20% du code nécessaire pour couvrez 100% de votre logique (c’est-à-dire que couvrir tous les cas extrêmes prend environ cinq fois plus de code de test unitaire). Bien sûr, en fonction de la structure, vous pouvez initialiser l’environnement pour plusieurs tests, ce qui réduit le code global nécessaire, mais cela nécessite une planification supplémentaire. S'approcher de 100% de confiance nécessite beaucoup plus de temps que de simplement tester les chemins principaux.
Phyrfox
2
@phyrfox Je pense que c'est trop prudent, cela ressemble plus à "l'autre 99% du code est constitué de cas extrêmes". Ce qui signifie que l'autre 99% des tests sont pour ces cas extrêmes.
Móż
@ Nelson Je conviens que les opérateurs ternaires imbriqués sont difficiles à lire, mais je ne pense pas qu'ils rendent les tests particulièrement difficiles (un bon outil de couverture vous dira si vous avez oublié l'une des combinaisons possibles). OMI, le logiciel est difficile à tester lorsqu'il est trop étroitement couplé, ou dépend de données câblées ou de données non transmises en tant que paramètre (par exemple, lorsqu'une condition dépend de l'heure actuelle et qu'elle n'est pas transmise en tant que paramètre). Ce n'est pas directement lié à la "lisibilité" du code, bien que, toutes choses égales par ailleurs, le code lisible est meilleur!
Andres F.
96

Il est.

Même si vous ne faites que des tests unitaires, il n’est pas inhabituel d’avoir plus de code dans les tests que le code réellement testé. Il n'y a rien de mal à cela.

Considérons un code simple:

public void SayHello(string personName)
{
    if (personName == null) throw new NullArgumentException("personName");

    Console.WriteLine("Hello, {0}!", personName);
}

Quels seraient les tests? Il y a au moins quatre cas simples à tester ici:

  1. Le nom de la personne est null. Une exception est-elle réellement levée? Cela fait au moins trois lignes de code de test à écrire.

  2. Le nom de la personne est "Jeff". Sommes-nous "Hello, Jeff!"en réponse? C'est quatre lignes de code de test.

  3. Le nom de la personne est une chaîne vide. À quelle sortie attendons-nous? Quelle est la sortie réelle? Question secondaire: cela correspond-il aux exigences fonctionnelles? Cela signifie quatre autres lignes de code pour le test unitaire.

  4. Le nom de la personne est suffisamment court pour une chaîne, mais trop long pour être combiné avec "Hello, "le point d'exclamation. Qu'est-ce qui se passe? ¹

Cela nécessite beaucoup de code de test. De plus, les éléments de code les plus élémentaires nécessitent souvent un code de configuration qui initialise les objets nécessaires au code testé, ce qui conduit souvent à l'écriture de stubs et de mocks, etc.

Si le rapport est très grand, dans ce cas, vous pouvez vérifier quelques points:

  • Existe-t-il une duplication de code dans les tests? Le fait qu'il s'agisse d'un code de test ne signifie pas que le code doit être dupliqué (copier-coller) entre des tests similaires: une telle duplication rendra difficile la maintenance de ces tests.

  • Y a-t-il des tests redondants? En règle générale, si vous supprimez un test unitaire, la couverture des branches devrait diminuer. Si ce n'est pas le cas, cela peut indiquer que le test n'est pas nécessaire, car les chemins sont déjà couverts par d'autres tests.

  • Testez-vous uniquement le code que vous devriez tester? Vous n'êtes pas censé tester la structure sous-jacente des bibliothèques tierces, mais uniquement le code du projet lui-même.

Avec les tests de fumée, les tests de système et d'intégration, les tests de fonctionnement et d'acceptation, ainsi que les tests de contrainte et de charge, vous ajoutez encore plus de code de test. Il ne faut donc pas s'inquiéter d'avoir quatre ou cinq LOCs de tests pour chaque LOC de code réel.

Une note sur le TDD

Si vous êtes préoccupé par le temps nécessaire pour tester votre code, il se peut que vous le fassiez mal, c'est-à-dire le code d'abord, puis les tests plus tard. Dans ce cas, TDD peut vous aider en vous encourageant à travailler dans des itérations de 15 à 45 secondes, en alternant code et tests. Selon les partisans de TDD, cela accélère le processus de développement en réduisant à la fois le nombre de tests que vous devez faire et, plus important encore, le nombre de codes d’entreprise à écrire et surtout la réécriture des tests.


¹ Soit n soit la longueur maximale d'une chaîne . Nous pouvons appeler SayHelloet transmettre par référence une chaîne de longueur n - 1 qui devrait fonctionner correctement. Maintenant, à l’ Console.WriteLineétape, le formatage doit se terminer par une chaîne de longueur n + 8, ce qui entraînera une exception. Peut-être, en raison des limites de mémoire, même une chaîne contenant n / 2 caractères entraînera une exception. La question à se poser est de savoir si ce quatrième test est un test unitaire (il en a l'air, mais peut avoir un impact beaucoup plus important en termes de ressources que les tests unitaires moyens) et s'il teste le code réel ou le framework sous-jacent.

Arseni Mourzenko
la source
5
N'oubliez pas qu'une personne peut aussi avoir le nom null. stackoverflow.com/questions/4456438/…
psatek
1
@JacobRaihle Je suppose que @MainMa signifie la valeur de personNamefits in a string, mais la valeur de personNameplus les valeurs concaténées débordent string.
mars
@JacobRaihle: J'ai modifié ma réponse pour expliquer ce point. Voir la note de bas de page.
Arseni Mourzenko
4
As a rule of thumb, if you remove a unit test, the branch coverage should decrease.Si j'écris les quatre tests que vous avez mentionnés ci-dessus, puis que je supprime le troisième test, la couverture diminuera-t-elle?
Vivek
3
"assez longtemps" → "trop ​​long" (au point 4)?
Paŭlo Ebermann
59

Je pense qu'il est important de distinguer deux types de stratégies de test: le test unitaire et le test d'intégration / acceptation.

Bien que les tests unitaires soient nécessaires dans certains cas, ils sont souvent excessivement complétés. Ceci est exacerbé par des métriques sans signification imposées aux développeurs, comme "couverture à 100%". http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf fournit un argument convaincant à cet égard. Considérez les problèmes suivants avec les tests unitaires agressifs:

  • Une multitude de tests inutiles qui ne documentent pas une valeur commerciale, mais existent simplement pour se rapprocher de cette couverture à 100%. Là où je travaille, nous devons écrire des tests unitaires pour des usines qui ne font que créer de nouvelles instances de classes. Cela n'ajoute aucune valeur. Ou ces longues méthodes .equals () générées par Eclipse - il n'est pas nécessaire de les tester.
  • Afin de faciliter les tests, les développeurs diviseraient des algorithmes complexes en unités testables plus petites. Cela ressemble à une victoire, non? Pas si vous devez avoir 12 classes ouvertes pour suivre un chemin de code commun. Dans ces cas, les tests unitaires peuvent en réalité diminuer la lisibilité du code. Un autre problème avec ceci est que si vous découpez votre code en trop petits morceaux, vous vous retrouvez avec une magnitude de classes (ou de morceaux de code) qui ne semblent pas avoir de justification autre que celle d'être un sous-ensemble d'un autre morceau de code.
  • Refactoring d' un code hautement coveraged peut être difficile, car il faut aussi maintenir l'abondance des tests unitaires qui en dépendent travailler tellement . Ceci est exacerbé par les tests unitaires comportementaux, où une partie de votre test vérifie également l'interaction des collaborateurs d'une classe (généralement simulée).

Par ailleurs, les tests d’intégration / d’acceptation constituent un élément extrêmement important de la qualité des logiciels et, d’après mon expérience, vous devriez consacrer beaucoup de temps à les corriger.

De nombreux magasins ont bu le TDD Kool-Aid, mais comme le montre le lien ci-dessus, un certain nombre d’études montrent que ses avantages ne sont pas concluants.

firtydank
la source
10
+1 pour le point sur la refactorisation. Auparavant, je travaillais sur un produit existant patché et corrigé depuis plus de dix ans. Juste essayer de déterminer les dépendances pour une méthode particulière pourrait prendre une bonne partie de la journée, puis essayer de trouver comment se moquer d’eux pourrait prendre encore plus de temps. Il n’était pas inhabituel qu’un changement de cinq lignes exige plus de 200 lignes de code d’essai et demande une bonne partie de la semaine.
TMN
3
Cette. Dans le test de réponse 4 de MainMa, vous ne devez pas effectuer de test (en dehors d'un contexte académique), car songez à la façon dont cela se produirait dans la pratique ... si le nom d'une personne est proche de la taille maximale d'une chaîne, quelque chose a mal tourné. Ne testez pas, dans la plupart des cas, ne disposez pas d'un chemin de code pour le détecter. La réponse appropriée consiste à laisser le framework renvoyer l'exception de mémoire insuffisante sous-jacente, car c'est le problème réel.
Móż
3
Je vous encourage jusqu'à "inutile de tester ces longues .equals()méthodes générées par Eclipse." J'ai écrit un test harnais pour equals()et compareTo() github.com/GlenKPeterson/TestUtils. Presque toutes les implémentations que j'ai testées manquaient. Comment utilisez-vous les collections si equals()et hashCode()ne travaillent pas ensemble correctement et efficacement? J'encourage à nouveau le reste de votre réponse et je l'ai votée favorablement. Je reconnais même que certaines méthodes equals () générées automatiquement n'ont peut-être pas besoin de tests, mais j'ai eu tellement de bogues avec une mise en œuvre médiocre que cela me rend nerveux.
GlenPeterson
1
@GlenPeterson D'accord. Un de mes collègues a écrit EqualsVerifier à cette fin. Voir github.com/jqno/equalsverifier
Tohnmeister
@ Σᶎ Non, vous devez toujours tester les entrées inacceptables, c'est ainsi que les gens trouvent les failles de sécurité.
gbjbaanb
11

Ne peut pas être généralisé.

Si j'ai besoin d'implémenter une formule ou un algorithme à partir d'un rendu physique, il se peut très bien que je passe 10 heures sur des tests unitaires paranoïaques, car je sais que le moindre bogue ou une imprécision peut conduire à des bugs presque impossibles à diagnostiquer, des mois plus tard. .

Si je veux juste regrouper logiquement quelques lignes de code et lui donner un nom, utilisé uniquement dans l'étendue du fichier, je ne peux pas le tester du tout (si vous insistez pour écrire des tests pour chaque fonction, sans exception, les programmeurs risquent de se replier écrire le moins de fonctions possible).

phresnel
la source
C'est un point de vue vraiment précieux. La question a besoin de plus de contexte pour pouvoir y répondre pleinement.
NSI
3

Oui, c'est normal si vous parlez de TDDing. Lorsque vous avez des tests automatisés, vous sécurisez le comportement souhaité de votre code. Lorsque vous écrivez vos tests en premier, vous déterminez si le code existant a déjà le comportement souhaité.

Cela signifie que:

  • Si vous écrivez un test qui échoue, la correction du code avec la méthode la plus simple qui fonctionne est plus rapide que l'écriture du test.
  • Si vous écrivez un test qui réussit, vous n'avez aucun code supplémentaire à écrire, vous passez donc plus de temps à écrire un test que le code.

(Cela ne tient pas compte de la refactorisation du code, qui vise à passer moins de temps à l'écriture du code suivant. Elle est contrebalancée par la refactorisation des tests, qui vise à passer moins de temps à la rédaction des tests suivants.)

Oui aussi, si vous parlez d'écrire des tests après coup, vous passerez plus de temps:

  • Déterminer le comportement souhaité.
  • Détermination des moyens de tester le comportement souhaité.
  • Répondre aux dépendances de code pour pouvoir écrire les tests.
  • Code de correction pour les tests qui échouent.

Que vous passerez réellement à écrire du code.

Alors oui, c'est une mesure attendue.

Laurent LA RIZZA
la source
3

Je trouve que c'est la partie la plus importante.

Les tests unitaires ne consistent pas toujours à "voir si cela fonctionne bien", mais à apprendre. Une fois que vous testez quelque chose, cela devient "codé en dur" dans votre cerveau et vous réduisez éventuellement le temps de test de votre unité et vous pouvez vous retrouver à écrire des classes et des méthodes entières sans rien tester jusqu'à ce que vous ayez terminé.

C'est pourquoi l'une des autres réponses sur cette page mentionne que dans un "cours", ils ont effectué des tests à 90%, car chacun devait apprendre les mises en garde relatives à son objectif.

Les tests unitaires ne sont pas seulement une utilisation très précieuse de votre temps, car ils améliorent littéralement vos compétences, c'est également un bon moyen de revoir votre propre code et de trouver une erreur logique en chemin.

Jesse
la source
2

Cela peut être pour beaucoup de gens, mais cela dépend.

Si vous écrivez d’abord des tests (TDD), il est possible que le chevauchement du temps passé à l’écriture du test soit réellement utile pour écrire le code. Considérer:

  • Détermination des résultats et des entrées (paramètres)
  • Conventions de nommage
  • Structure - où mettre les choses.
  • Vieille pensée

Lorsque vous écrivez des tests après avoir écrit le code, vous découvrirez peut-être que votre code n'est pas facilement testable. L'écriture de tests est donc plus difficile et prend plus de temps.

La plupart des programmeurs écrivent du code beaucoup plus longtemps que les tests, alors je m'attendrais à ce que la plupart d'entre eux ne soient pas aussi fluides. De plus, vous devez ajouter le temps nécessaire pour comprendre et utiliser votre infrastructure de test.

Je pense que nous devons changer notre état d'esprit en ce qui concerne le temps requis pour coder et la manière dont les tests unitaires sont impliqués. Ne le regardez jamais à court terme et ne comparez jamais le temps total nécessaire pour fournir une fonctionnalité particulière, car vous devez tenir compte non seulement de l'écriture d'un code meilleur / moins bogué, mais également d'un code plus facile à modifier et à conserver. mieux / moins buggy.

À un moment donné, nous ne sommes tous capables que de rédiger un code aussi bon. Certains outils et techniques ne peuvent donc offrir que beaucoup pour améliorer nos compétences. Ce n'est pas comme si je pouvais construire une maison si je n'avais qu'une scie guidée au laser.

JeffO
la source
2

Est-il normal de consacrer autant, sinon plus, de temps à la rédaction de tests que le code réel?

Oui, ça l'est. Avec quelques mises en garde.

Tout d’abord, c’est "normal" dans le sens où la plupart des grands magasins fonctionnent de cette façon. Même si cette façon de procéder était complètement erronée et idiote, le fait que la plupart des grands magasins fonctionnent de cette manière le rend "normal".

Par cela, je ne veux pas dire que les tests sont faux. J'ai travaillé dans des environnements sans tests et dans des environnements avec des tests obsessionnels compulsifs, et je peux toujours vous dire que même les tests obsessionnels compulsifs étaient meilleurs que l'absence de tests.

Et je ne fais pas encore TDD (qui sait, je le ferai peut-être à l'avenir), mais je fais la grande majorité de mes cycles de montage-exécution-débogage en exécutant les tests, pas l'application réelle, alors naturellement, je travaille beaucoup sur mes tests, afin d'éviter autant que possible d'avoir à exécuter l'application proprement dite.

Sachez toutefois que des tests excessifs présentent des dangers, notamment le temps consacré à leur maintenance . (J'écris principalement cette réponse pour le préciser.)

Dans la préface de L'Art des tests unitaires de Roy Osherove (Manning, 2009), l'auteur reconnaît avoir participé à un projet qui a échoué en grande partie en raison de l'énorme fardeau de développement imposé par des tests unitaires mal conçus qu'il a fallu maintenir tout au long du processus. durée de l'effort de développement. Donc, si vous passez trop de temps à ne faire que maintenir vos tests, cela ne signifie pas nécessairement que vous êtes sur la bonne voie, car c'est "normal". Votre effort de développement est peut-être entré dans un mode malsain, où il pourrait être nécessaire de repenser radicalement votre méthodologie de test afin de sauver le projet.

Mike Nakis
la source
0

Est-il normal de consacrer autant, sinon plus, de temps à la rédaction de tests que le code réel?

  • Oui pour l'écriture de tests unitaires (Tester un module de manière isolée) si le code est fortement couplé (héritage, séparation insuffisante des préoccupations , injection de dépendance manquante , tdd non développé )
  • Oui pour l'écriture de tests d'intégration / d'acceptation si la logique à tester est uniquement accessible via un code d'interface graphique
  • Non pour l'écriture de tests d'intégration / d'acceptation tant que le code d'interface graphique et la logique applicative sont séparés (le test n'a pas besoin d'interagir avec l'interface graphique)
  • Non pour l'écriture de tests unitaires s'il existe une séparation des préoccupations, une injection de dépendance, le code a été développé sur test (tdd)
k3b
la source