quels types de fonctions et / ou classes sont impossibles à tester unitairement et pourquoi

21

L'excuse principale du développeur pour ne pas avoir un bon test unitaire est "Le code n'est pas conçu de manière testable unitaire". J'essaie de comprendre quel type de conception et de code ne peut pas être testé à l'unité.

manizzzz
la source
2
Votre excuse? Un collègue? Des managers? Avec quel langage / framework travaillez-vous?
1
Beaucoup de code hérité dans l'application et pas de temps pour la refonte.
knut
4
@gnat: Je ne suis pas d'accord. La question que vous avez citée concerne les situations dans lesquelles les tests unitaires ne sont pas utiles. La question actuelle concerne les situations qui rendent les tests unitaires difficiles.
Arseni Mourzenko
@MainMa apparemment, nous avons lu différentes questions. "J'essaie de comprendre quel type de conception et de code ne peut pas être testé à l'unité." => "Quand est-il approprié de ne pas effectuer de test unitaire?"
moucher
1
@manizzzz: vous voudrez peut-être modifier cela dans la question.
jmoreno

Réponses:

27

Plusieurs facteurs peuvent rendre le code difficile à tester unitaire. Dans ce cas, le refactoring permet d'améliorer le code afin qu'il soit testable.

Quelques exemples de code qui seraient probablement difficiles à tester:

  • Une fonction 1000-LOC,
  • Code qui s'appuie fortement sur l'état mondial,
  • Code qui nécessite du concret, compliqué pour construire des objets, comme le contexte de base de données, au lieu de s'appuyer sur des interfaces et l'injection de dépendances,
  • Code qui fonctionne lentement ,
  • Code de spaghetti,
  • Code hérité qui a été modifié pendant des années sans souci de lisibilité ou de maintenabilité,
  • Difficile de comprendre le code qui n'a pas de commentaires ou d'indications sur l'intention originale de l'auteur (par exemple, le code qui utilise des noms de variables tels que function pGetDp_U(int i, int i2, string sText).

Notez que le manque d'architecture claire ne rend pas le code difficile à tester unitaire, car les tests unitaires concernent de petites parties du code. Une architecture peu claire aurait toujours un impact négatif sur l'intégration et les tests du système.

Arseni Mourzenko
la source
8
Il est également difficile de tester du code qui n'injecte pas de dépendances sur des fonctions non pures, comme les nombres aléatoires, l'heure actuelle, les E / S câblées, etc.
9000
son trivial pour tester le code comme ça - vous avez juste besoin du bon outil de test, de ne pas modifier votre code à leur convenance. Essayez Microsoft Fakes pour un exemple.
gbjbaanb
@MainMa, j'aime cette réponse. Seriez-vous également disposé à commenter un peu les facteurs qui poussent les différents tests vers les tests d'intégration et de système? Je sais que la raison pour laquelle j'ai posé des questions similaires à celle ici dans le passé est que je n'avais pas de feuille de route expliquant quels types de tests sont les mieux placés où (ou peut-être les plus rentables où) - je pensais les tests unitaires étaient les seuls.
J Trana
14

Il y a beaucoup de choses qui rendent le code difficile à tester unitaire. Par coïncidence, beaucoup de ceux-ci rendent également le code difficile à maintenir:

  • Loi sur les violations de Demeter .
  • Création d'objets à l'intérieur d'une méthode au lieu d' injecter des dépendances .
  • Couplage serré.
  • Mauvaise cohésion.
  • Dépend fortement des effets secondaires.
  • S'appuie fortement sur les globaux ou singletons.
  • N'expose pas beaucoup de résultats intermédiaires. (J'ai dû tester une fois une fonction mathématique de dix pages avec une seule sortie et aucun résultat intermédiaire disponible. Mes prédécesseurs ont essentiellement codé en dur la réponse que le code avait donnée).
  • Dépend fortement et directement de services difficiles à simuler, comme les bases de données.
  • L'environnement d'exécution est considérablement différent de l'environnement de développement, comme une cible intégrée.
  • Unités uniquement disponibles sous forme compilée (comme une DLL tierce).
Karl Bielefeldt
la source
Je pense que c'est une excellente réponse. Vous touchez à de nombreux problèmes de niveau de code et à des problèmes d'état globaux. @MainMa a d'autres problèmes qui, je pense, sont valables, mais moins bien définis. Jeffery Thomas mentionne les E / S et l'interface utilisateur. Je pense que si vous ajoutez les bonnes parties de ces trois réponses, vous obtiendrez une excellente réponse cohérente. Cependant, j'aime mieux cette réponse en raison de l'accent mis sur les anti-modèles de code.
M2tM
1
Argh - rien de pire qu'un test unitaire qui n'a aucune ressemblance avec les exigences de l'entreprise et n'est que la sortie à un moment donné - des simulations configurées pour être appelées 3 fois par exemple? Pourquoi 3? Parce que c'était 3 la première fois que le test a été exécuté / diatribe :)
Michael
Un couplage serré n'est mauvais que lorsqu'il est inapproprié. Un couplage serré dans le code qui est hautement cohésif est une nécessité. Par exemple une déclaration de variable suivie de son utilisation. Étroitement couplé, très cohésif.
dietbuddha
1
Les tests unitaires où la sortie est vérifiée par rapport à ce que le code a fait, sans analyse de rentabilisation / justification, sont appelés tests de caractérisation. Ils sont utilisés dans la maintenance où il n'y avait auparavant aucun test et souvent aucune exigence documentée et vous devez mettre quelque chose qui se cassera si la sortie de cette fonction change. Ils valent mieux que rien.
Andy Krouwel
5

Exemples courants de code que les gens ne souhaitent pas tester unitaire:

  • Code qui interagit directement avec les E / S (lecture de fichiers, appels réseau directs,…).
  • Code qui met directement à jour l'interface utilisateur.
  • Code qui fait directement référence à des singletons ou à des objets globaux.
  • Code qui modifie implicitement l'état de l'objet ou du sous-objet.

En utilisant un cadre factice, tous ces exemples peuvent être testés à l'unité. Il suffit de configurer les faux remplacements pour les dépendances internes.

Des choses qui ne peuvent vraiment pas être testées à l'unité:

  • Boucles infinies (pour un gestionnaire de threads, un pilote ou un autre type de code de longue durée)
  • Certains types d'opérations d'assemblage direct (pris en charge par certaines langues)
  • Code qui nécessite un accès privilégié (pas impossible, juste pas une bonne idée)
Jeffery Thomas
la source
2

Il existe quelques domaines pour lesquels il est plus difficile d'écrire des tests unitaires. Cependant, je tiens à souligner que cela ne signifie pas que vous devez ignorer les techniques utiles tout de suite simplement parce qu'elles peuvent ajouter une certaine complexité à vos tests. Comme avec tout codage, vous devriez faire votre propre analyse pour déterminer si les avantages dépassent les coûts, et ne pas accepter aveuglément ce que certains gars aléatoires publient sur le net.

Mal écrit du code conçu

  • couplage inapproprié (généralement couplage serré là où il ne devrait pas être)
  • code d'évier de cuisine (où une fonction a beaucoup trop de logique / responsabilités)

Dépendance de l'État dans une portée différente

Le coût de la plupart de ces spirales est incontrôlable, sauf si vous savez ce que vous faites. Malheureusement, beaucoup ne savent souvent pas comment utiliser ces techniques de manière à atténuer des choses comme les tests de complexité.

  • Singletons
  • Globals
  • Fermetures

État externe / système

  • Dépendances matériel / périphérique
  • Dépendances réseau
  • Dépendances du système de fichiers
  • Dépendances entre processus
  • Autres dépendances des appels système

Accès simultané

  • Filetage (verrous, sections critiques, etc.)
  • fourche
  • Coroutines
  • Rappels
  • Gestionnaires de signaux (pas tous, mais certains)
Dietbuddha
la source
2

Le code ne peut pas être testé. Il existe cependant quelques exemples de code qui sont VRAIMENT, VRAIMENT difficiles à tester (au point de ne pas en valoir la peine):

Interactions matérielles - Si le code manipule directement le matériel (par exemple, écrire dans un registre pour déplacer un périphérique physique), les tests unitaires peuvent être trop difficiles ou coûteux. Si vous utilisez du matériel réel pour le test, cela peut coûter cher pour obtenir des commentaires appropriés dans le faisceau de test (encore plus d'équipement!), Et si vous ne le faites pas, vous devez émuler le comportement exact des objets physiques - pas une petite astuce certains cas.

Interactions d'horloge - Ceci est généralement plus facile, car il est presque toujours possible de simuler les fonctions d'horloge du système de manière assez triviale. Mais lorsque vous ne le pouvez pas, ces tests deviennent ingérables - les tests basés sur le temps réel ont tendance à prendre beaucoup de temps à s'exécuter, et d'après mon expérience, ils ont tendance à être très fragiles car les charges du système rendent les choses plus longues qu'elles ne le devraient , provoquant des échecs du test fantôme.

Michael Kohne
la source
0

Mes trois principaux groupes pour cela sont:

  • code qui s'appuie sur des services externes

  • les systèmes qui ne permettent pas aux testeurs de modifier l'état indépendamment de l'application.

  • environnements de test qui ne reproduisent pas la configuration de production.

C'est ce que j'ai vécu le plus en tant que développeur devenu ingénieur QA.

Michael Durrant
la source