Donc, aujourd’hui, j’ai parlé avec mon coéquipier au sujet des tests unitaires. Tout a commencé quand il m'a demandé "hé, où sont les tests pour ce cours, je n'en vois qu'un?". Toute la classe était un manager (ou un service si vous préférez l'appeler ainsi) et presque toutes les méthodes consistaient simplement à déléguer des tâches à un DAO. Cela ressemblait donc à:
SomeClass getSomething(parameters) {
return myDao.findSomethingBySomething(parameters);
}
Une sorte de passe-partout sans logique (ou du moins, je ne considère pas cette simple délégation comme une logique), mais un passe-passe utile dans la plupart des cas (séparation des couches, etc.). Et nous avons eu une assez longue discussion sur la question de savoir si je devais ou non effectuer un test unitaire (je pense qu’il est utile de mentionner que j’ai fait le test unitaire complet du DAO). Ses arguments principaux sont qu’il ne s’agissait pas de TDD (à l’évidence) et que quelqu'un voudrait peut-être voir le test pour vérifier ce que fait cette méthode (je ne sais pas comment cela pourrait être plus évident), ou qu’à l’avenir quelqu'un voudra peut-être modifier la méthode. implémentation et y ajouter une nouvelle logique (ou plus semblable à "n’importe laquelle") (auquel cas je suppose que quelqu'un devrait simplement tester cette logique ).
Cela m'a fait penser, cependant. Devrions-nous nous efforcer d’obtenir le taux de couverture de test le plus élevé? Ou est-ce simplement un art pour l'art alors? Je ne vois tout simplement aucune raison de tester des choses comme:
- les getters et les setters (à moins qu'ils aient une logique en eux)
- code "passe-partout"
Évidemment, un test pour une telle méthode (avec des simulacres) me prendrait moins d'une minute, mais j'imagine que c'est toujours du temps perdu et une milliseconde de plus pour chaque IC.
Y a-t-il des raisons rationnelles / non "inflammables" pour lesquelles on devrait tester chaque ligne de code (ou autant qu'il peut)?
la source
Réponses:
Je me fie à la règle de base de Kent Beck:
Testez tout ce qui pourrait éventuellement casser.
Bien sûr, c'est subjectif dans une certaine mesure. Pour moi, les getters / setters et les one-liners triviaux comme le vôtre ne valent généralement rien. Mais là encore, je passe le plus clair de mon temps à écrire des tests unitaires pour le code hérité, ne rêvant que d’un beau projet TDD dans un environnement vierge ... Sur de tels projets, les règles sont différentes. Avec le code existant, l’objectif principal est de couvrir le plus de terrain possible avec le moins d’effort possible. Les tests unitaires ont donc tendance à être de niveau plus élevé et plus complexes, plus comme des tests d’intégration si on est pédant sur la terminologie. Et lorsque vous avez du mal à obtenir une couverture globale du code supérieure à 0%, ou que vous réussissez à la dépasser de plus de 25%, le test de vos unités est le dernier de vos soucis.
OTOH dans un nouveau projet TDD, il peut être plus pratique d'écrire des tests même pour de telles méthodes. D'autant plus que vous avez déjà passé le test avant d'avoir la chance de commencer à vous demander "est-ce que cette ligne mérite un test dédié?". Et au moins, ces tests sont faciles à écrire et rapides à exécuter. Ce n'est donc pas grave.
la source
Il existe peu de types de tests unitaires:
Si vous deviez écrire votre test en premier, cela aurait plus de sens - comme vous pouvez vous attendre à appeler une couche d'accès aux données. Le test échouerait initialement. Vous écririez ensuite le code de production pour réussir le test.
Idéalement, vous devriez tester le code logique, mais les interactions (objets appelant d'autres objets) sont également importantes. Dans votre cas, je voudrais
Actuellement, il n'y a pas de logique là-bas, mais ce ne sera pas toujours le cas.
Cependant, si vous êtes sûr qu'il n'y aura pas de logique dans cette méthode et qu'elle restera probablement inchangée, je considérerais d'appeler la couche d'accès aux données directement à partir du consommateur. Je ne ferais cela que si le reste de l'équipe est sur la même page. Vous ne voulez pas envoyer un message erroné à l'équipe en lui disant "Hé les gars, il est bon d'ignorer la couche de domaine, appelez simplement la couche d'accès aux données directement".
Je me concentrerais également sur le test d'autres composants s'il y avait un test d'intégration pour cette méthode. Je n'ai pas encore vu une entreprise avec des tests d'intégration solides cependant.
Cela dit, je ne testerais pas aveuglément tout. J'établirais les points chauds (composants très complexes et risquant de se casser). Je me concentrerais ensuite sur ces composants. Il ne sert à rien d'avoir une base de code où 90% de la base de code est assez simple et couverte par les tests unitaires, les 10% restants représentant la logique centrale du système et ne le sont pas en raison de leur complexité.
Enfin, quel est l'intérêt de tester cette méthode? Quelles sont les implications si cela ne fonctionne pas? Sont-ils catastrophiques? N'essayez pas d'obtenir une couverture de code élevée. La couverture de code devrait être un sous-produit d'une bonne suite de tests unitaires. Par exemple, vous pouvez écrire un test qui parcourt l’arbre et vous donner une couverture de 100% de cette méthode, ou vous pouvez écrire trois tests unitaires qui vous donneront également une couverture de 100%. La différence est qu'en écrivant trois tests, vous testez des cas extrêmes, au lieu de simplement parcourir l'arborescence.
la source
Voici un bon moyen de réfléchir à la qualité de votre logiciel:
Pour les fonctions standard et triviales, vous pouvez vous fier à la vérification de type, et pour le reste, vous avez besoin de scénarios de test.
la source
À mon avis, la complexité cyclomatique est un paramètre. Si une méthode n'est pas assez complexe (comme les accesseurs et les setters). Aucun test unitaire n'est nécessaire. Le niveau de complexité cyclomatique de McCabe devrait être supérieur à 1. Un autre mot devrait comporter au moins 1 énoncé bloc.
la source
Un retentissant OUI avec TDD (et à quelques exceptions près)
Bien controversé, mais je dirais que quiconque répond «non» à cette question manque un concept fondamental du TDD.
Pour moi, la réponse est un oui retentissant si vous suivez TDD. Si vous ne l'êtes pas, non est une réponse plausible.
La DDD en TDD
TDD est souvent cité comme ayant les principaux avantages.
Séparer la responsabilité de la mise en œuvre
En tant que programmeurs, il est terriblement tentant de considérer les attributs comme quelque chose d’important et d’attirer et de définir une sorte de surcharge.
Mais les attributs sont un détail d'implémentation, tandis que les setters et les getters sont l'interface contractuelle qui permet aux programmes de fonctionner.
Il est bien plus important d’épeler qu’un objet doit:
et
puis comment cet état est réellement stocké (pour lequel un attribut est le plus commun, mais pas le seul moyen).
Un test tel que
est important pour la partie documentation de TDD.
Le fait que la mise en œuvre éventuelle soit triviale (attribut) et ne comporte aucun avantage en termes de défense devrait vous être inconnu lorsque vous écrivez le test.
Le manque d'ingénierie aller-retour ...
L'un des problèmes majeurs du monde du développement de systèmes est le manque d' ingénierie aller-retour 1 - le processus de développement d'un système est fragmenté en sous-processus disjoints dont les artefacts (documentation, code) sont souvent incohérents.
1 Brodie, Michael L. "John Mylopoulos: coudre des graines de modélisation conceptuelle." Modélisation conceptuelle: fondements et applications. Springer Berlin Heidelberg, 2009. 1-9.
... et comment TDD le résout
C’est la partie documentation de TDD qui garantit la cohérence des spécifications du système et de son code.
Concevoir d'abord, mettre en œuvre plus tard
Dans TDD, nous écrivons d’abord le test d’acceptation ayant échoué, puis nous écrivons le code qui les laisse passer.
Au sein du BDD de niveau supérieur, nous écrivons d’abord des scénarios, puis nous les faisons passer.
Pourquoi devriez-vous exclure les setters et les getter?
En théorie, au sein de TDD, il est parfaitement possible à une personne d’écrire le test et à une autre d’implémenter le code qui le fait passer.
Alors demandez-vous:
Comme les getters et les setters sont une interface publique avec une classe, la réponse est évidemment oui , sinon il n'y aura aucun moyen de définir ou d'interroger l'état d'un objet.
De toute évidence, si vous écrivez le code en premier, la réponse risque de ne pas être aussi claire.
Exceptions
Il existe des exceptions évidentes à cette règle - des fonctions qui sont détaillées dans la mise en œuvre et qui ne font manifestement pas partie de la conception du système.
Par exemple, a la méthode locale 'B ()':
Ou la fonction privée
square()
ici:Ou toute autre fonction ne faisant pas partie d'une
public
interface nécessitant une orthographe dans la conception du composant système.la source
Face à une question philosophique, revenez aux exigences de conduite.
Votre objectif est-il de produire des logiciels raisonnablement fiables à un coût compétitif?
Ou est-ce pour produire un logiciel avec la plus grande fiabilité possible, quel que soit le coût?
Jusqu'à un certain point, les deux objectifs de qualité et de vitesse / coût de développement s'alignent: vous passez moins de temps à écrire des tests qu'à réparer des défauts.
Mais au-delà, ils ne le font pas. Il n’est pas si difficile d’obtenir par exemple un bug signalé par développeur et par mois. Réduire de moitié ce nombre en un mois sur deux ne libère qu'un budget d'environ un jour ou deux, et de nombreux tests supplémentaires ne réduiront probablement pas votre taux de défauts. Donc, ce n'est plus un simple gagnant / gagnant; vous devez le justifier en fonction du coût du défaut pour le client.
Ce coût variera (et, si vous voulez être pervers, leur capacité à vous faire supporter ces coûts, que ce soit par le biais du marché ou par le biais d'une action en justice). Vous ne voulez pas être méchant, alors vous comptez ces coûts dans leur intégralité; parfois, certains tests continuent globalement à rendre le monde plus pauvre par leur existence.
En bref, si vous essayez d’appliquer aveuglément les mêmes normes à un site Web interne que le logiciel de vol pour avion de ligne, vous vous retrouverez soit en faillite, soit en prison.
la source
Votre réponse à ce sujet dépend de votre philosophie (croyez-vous que ce sera Chicago vs Londres? Je suis sûr que quelqu'un le vérifiera). Le jury n’a toujours pas choisi l’approche la plus efficace en termes de temps (car, après tout, c’est le facteur le plus déterminant de la réduction du temps passé sur les solutions).
Certaines approches ne testent que l'interface publique, d'autres testent l'ordre de chaque appel de fonction dans chaque fonction. Beaucoup de guerres saintes ont été menées. Mon conseil est d'essayer les deux approches. Choisissez une unité de code et faites-la comme X, et une autre comme Y. Après quelques mois de test et d'intégration, revenez en arrière pour voir laquelle correspond le mieux à vos besoins.
la source
C'est une question délicate.
Strictement parlant, je dirais que ce n'est pas nécessaire. Il est préférable d’écrire des tests au niveau système et au niveau système BDD pour s’assurer que les exigences de l’entreprise fonctionnent comme prévu dans les scénarios positifs et négatifs.
Cela dit, si votre méthode n'est pas couverte par ces cas de test, vous devez vous demander pourquoi elle existe et si elle est nécessaire, ou si le code contient des exigences cachées qui ne sont pas reflétées dans votre documentation ou dans les user stories. devrait être codé dans un scénario de test de style BDD.
Personnellement, j'aime bien garder la couverture par ligne à environ 85-95% et laisser les enregistrements à la ligne principale pour s'assurer que la couverture de test unitaire existante par ligne atteint ce niveau pour tous les fichiers de code et qu'aucun fichier n'est découvert.
En supposant que les meilleures pratiques de test soient suivies, cela donne beaucoup de couverture sans obliger les développeurs à perdre du temps à essayer de trouver comment obtenir une couverture supplémentaire sur du code difficile à exercer ou un code trivial simplement pour le plaisir de la couverture.
la source
Le problème est la question elle-même, vous n'avez pas besoin de tester tous les "methdos" ou toutes les "classes" dont vous avez besoin pour tester toutes les fonctionnalités de vos systèmes.
Sa pensée clé en termes de caractéristiques / comportements au lieu de penser en termes de méthodes et de classes. Bien sûr, une méthode est là pour fournir un support pour une ou plusieurs fonctionnalités, à la fin tout votre code est testé, au moins tout le code est important dans votre base de code.
Dans votre scénario, probablement cette classe "manager" est redondante ou inutile (comme toutes les classes dont le nom contient le mot "manager"), ou peut-être pas, mais semble être un détail d'implémentation, probablement cette classe ne mérite pas une unité test car cette classe n'a pas de logique métier pertinente. Vous avez probablement besoin de cette classe pour que certaines fonctionnalités fonctionnent, le test de cette fonctionnalité couvre cette classe. Vous pouvez ainsi refactoriser cette classe et faire en sorte que ce qui compte, vos fonctionnalités, fonctionne toujours après le refactor.
Pensez dans les fonctionnalités / comportements non dans les classes de méthodes, je ne peux pas le répéter suffisamment de fois.
la source
Oui, idéalement à 100%, mais certaines choses ne sont pas testables à l’unité.
Les Getters / Setters sont stupides - mais ne les utilisez pas. Au lieu de cela, mettez votre variable de membre à la section publique.
Extrayez le code commun et testez-le à l'unité. Cela devrait être aussi simple que cela.
En ne le faisant pas, vous risquez de rater des bugs très évidents. Les tests unitaires sont comme un filet sûr pour attraper certains types de bugs, et vous devriez les utiliser autant que possible.
Et la dernière chose: je suis sur un projet où les gens ne voulaient pas perdre leur temps à écrire des tests unitaires pour du "code simple", mais ils ont ensuite décidé de ne pas écrire du tout. À la fin, certaines parties du code se sont transformées en une grosse boule de boue .
la source