web-dev-qa-db-fra.com

Faut-il tester toutes nos méthodes?

Aujourd'hui, j'ai discuté avec mon coéquipier des tests unitaires. Tout a commencé quand il m'a demandé "hé, où sont les tests pour cette classe, je n'en vois qu'un?". Toute la classe était un manager (ou un service si vous préférez l'appeler comme ça) et presque toutes les méthodes déléguaient simplement des choses à un DAO donc c'était similaire à:

SomeClass getSomething(parameters) {
    return myDao.findSomethingBySomething(parameters);
}

Une sorte de passe-partout sans logique (ou du moins je ne considère pas une délégation aussi simple que logique) mais un passe-partout utile dans la plupart des cas (séparation des couches, etc.). Et nous avons eu une discussion assez longue sur la question de savoir si je devrais le tester unitaire (je pense qu'il vaut la peine de mentionner que j'ai fait un test unitaire complet du DAO). Ses principaux arguments étant qu'il ne s'agissait pas de TDD (évidemment) et que quelqu'un pourrait vouloir 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 pourrait vouloir changer le implémentation et ajouter une nouvelle logique (ou plus comme "any") (auquel cas je suppose que quelqu'un devrait simplement tester cela logique).

Cela m'a cependant fait réfléchir. Faut-il viser le pourcentage 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 en fait une certaine logique)
  • code "passe-partout"

Évidemment, un test pour une telle méthode (avec des simulations) me prendrait moins d'une minute, mais je suppose que c'est encore du temps perdu et une milliseconde de plus pour chaque CI.

Y a-t-il des raisons rationnelles/non "inflammables" pour lesquelles on devrait tester chaque ligne de code (ou autant qu'il le peut)?

63
Zenzen

Je me fie à la règle empirique de Kent Beck:

Testez tout ce qui pourrait casser.

Bien sûr, c'est subjectif dans une certaine mesure. Pour moi, les getters/setters triviaux et les one-liners comme le vôtre ci-dessus ne valent généralement pas la peine. Mais là encore, je passe la plupart de mon temps à écrire des tests unitaires pour le code hérité, ne rêvant que d'un projet TDD Nice greenfield ... Sur de tels projets, les règles sont différentes. Avec le code hérité, l'objectif principal est de couvrir autant de terrain avec le moins d'effort possible, de sorte que les tests unitaires ont tendance à être de niveau supérieur et plus complexes, plus comme les tests d'intégration si l'on est pédant sur la terminologie. Et lorsque vous avez du mal à obtenir une couverture de code globale de 0%, ou que vous parvenez simplement à la dépasser de 25%, le test unitaire des getters et setters est le moindre de vos soucis.

OTOH dans un projet greenfield 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à écrit le test avant d'avoir la chance de commencer à vous demander "cette ligne vaut-elle un test dédié?". Et au moins ces tests sont triviaux à écrire et rapides à exécuter, donc ce n'est pas un gros problème de toute façon.

49
Péter Török

Il existe quelques types de tests unitaires:

  • Basé sur l'état. Vous agissez puis vous affirmez contre l'état de l'objet. Par exemple. Je fais un dépôt. Je vérifie ensuite si l'équilibre a augmenté.
  • Valeur de retour basée. Vous agissez et affirmez contre la valeur de retour.
  • Basé sur l'interaction. Vous vérifiez que votre objet a appelé un autre objet. Cela semble être ce que vous faites dans votre exemple.

Si vous deviez d'abord écrire votre test, cela aurait plus de sens - comme vous vous attendez à appeler une couche d'accès aux données. Le test échouerait initialement. Vous écririez alors du 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 tout aussi importantes. Dans votre cas, je

  • Vérifiez que j'ai appelé la couche d'accès aux données avec le paramètre exact qui a été transmis.
  • Vérifiez qu'il n'a été appelé qu'une seule fois.
  • Vérifiez que je retourne exactement ce qui m'a été donné par la couche d'accès aux données. Sinon, je pourrais aussi bien retourner null.

Actuellement, il n'y a pas de logique, 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 la même, je considérerais d'appeler la couche d'accès aux données directement depuis le consommateur. Je ne ferais cela que si le reste de l'équipe est sur la même page. Vous ne voulez pas envoyer un mauvais message à l'équipe en disant "Hé les gars, c'est bien 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.

Cela dit, je ne testerais pas tout aveuglément. J'établirais les points chauds (composants à haute complexité et à haut risque de rupture). Je me concentrerais alors sur ces composants. Il est inutile d'avoir une base de code où 90% de la base de code est assez simple et couverte par des tests unitaires, alors que les 10% restants représentent la logique de base du système et ils ne sont pas couverts par des tests unitaires en raison de leur complexité.

Enfin, quel est l'avantage de tester cette méthode? Quelles sont les implications si cela ne fonctionne pas? Sont-ils catastrophiques? Ne vous efforcez pas d'obtenir une couverture de code élevée. La couverture du code devrait être un sous-produit d'une bonne suite de tests unitaires. Par exemple, vous pouvez écrire un test qui parcourra l'arbre et vous donnera une couverture à 100% de cette méthode, ou vous pouvez écrire trois tests unitaires qui vous donneront également une couverture à 100%. La différence est qu'en écrivant trois tests, vous testez des cas Edge, au lieu de simplement parcourir l'arbre.

13
CodeART

Voici une bonne façon de penser à la qualité de votre logiciel:

  1. la vérification de type gère une partie du problème.
  2. les tests géreront le reste

Pour les fonctions standard et triviales, vous pouvez compter sur la vérification de type pour faire son travail, et pour le reste, vous avez besoin de cas de test.

9
tp1

À mon avis, la complexité cyclomatique est un paramètre. Si une méthode n'est pas assez complexe (comme les getters et setters). Aucun test unitaire n'est nécessaire. Le niveau de complexité cyclomatique de McCabe doit être supérieur à 1. Un autre mot doit contenir au moins 1 bloc.

6
Fırat KÜÇÜK

Un OUI retentissant avec TDD (et à quelques exceptions près)

Bien controversé, mais je dirais que quiconque répond "non" à cette question manque un concept fondamental de TDD.

Pour moi, la réponse est un retentissant oui si vous suivez TDD. Si vous ne l'êtes pas, alors non est une réponse plausible.

Le DDD dans TDD

TDD est souvent cité comme ayant les principaux avantages.

  • Défense
    • Assurer le le code peut changer mais pas son comportement.
    • Cela permet la pratique toujours aussi importante de refactoring.
    • Vous gagnez ce TDD ou non.
  • Conception
    • Vous spécifiez ce que quelque chose doit faire, comment elle doit se comporter avant de l'implémenter elle.
    • Cela signifie souvent plus informé décisions de mise en œuvre.
  • Documentation
    • La suite de tests devrait servir de documentation spécification (exigences).
    • L'utilisation de tests à cette fin signifie que la documentation et la mise en œuvre sont toujours dans un état cohérent - un changement à l'un signifie un changement à l'autre. Comparez avec la conservation des exigences et la conception sur un document Word séparé.

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 de significatif et les getters et setter comme 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 fait réellement fonctionner les programmes.

Il est beaucoup plus important d'épeler qu'un objet doit:

Permettre à ses clients de changer son état

et

Autoriser ses clients à interroger son état

puis comment cet état est réellement stocké (pour lequel un attribut est le plus courant, mais pas le seul).

Un test tel que

(The Painter class) should store the provided colour

est important pour la partie documentation de TDD.

Le fait que l'implémentation éventuelle soit triviale (attribut) et ne comporte aucun avantage défense ne devrait pas vous être connu lorsque vous écrivez le test.

Le manque d'ingénierie aller-retour ...

L'un des principaux problèmes dans le 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.

1Brodie, Michael L. "John Mylopoulos: graines de couture de la 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 que les spécifications du système et son code sont toujours cohérents.

Concevez d'abord, implémentez plus tard

Dans TDD, nous écrivons d'abord le test d'acceptation échoué, puis écrivons le code qui les a laissés passer.

Dans le BDD de niveau supérieur, nous écrivons d'abord des scénarios, puis les faisons passer.

Pourquoi devriez-vous exclure les setters et les getter?

En théorie, il est parfaitement possible au sein de TDD pour une personne d'écrire le test, et une autre pour implémenter le code qui le fait passer.

Alors demandez-vous:

La personne qui écrit les tests d'une classe doit-elle mentionner les getters et setter?.

Les getters et setters étant une interface publique pour une classe, la réponse est évidemment yes, ou il n'y aura aucun moyen de définir ou d'interroger l'état d'un objet.

Évidemment, si vous écrivez d'abord le code, la réponse peut ne pas être aussi claire.

Exceptions

Il y a des exceptions évidentes à cette règle - des fonctions qui sont des détails de mise en œuvre clairs et qui ne font clairement pas partie de la conception du système.

Par exemple, une méthode locale 'B ()':

function A() {

    // B() will be called here    

    function B() {
        ...
    }
} 

Ou la fonction privée square() ici:

class Something {
private:
    square() {...}
public:
    addAndSquare() {...}
    substractAndSquare() {...}
}

Ou toute autre fonction qui ne fait pas partie d'une interface public qui a besoin d'orthographe dans la conception du composant système.

3
Izhaki

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 des logiciels de la plus haute fiabilité possible, peu importe 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 à rédiger des tests qu'à corriger des défauts.

Mais au-delà de ce point, ils ne le font pas. Il n'est pas si difficile d'accéder à, disons, un bogue signalé par développeur et par mois. Réduire ce nombre à un par deux mois ne libère qu'un budget d'un jour ou deux, et des tests supplémentaires ne diminueront probablement pas de moitié votre taux de défauts. Ce n'est donc plus un simple gagnant/gagnant; vous devez le justifier sur la base du coût du défaut pour le client.

Ce coût variera (et, si vous voulez être mauvais, leur capacité à vous faire appliquer ces coûts, que ce soit par le biais du marché ou d'une action en justice). Vous ne voulez pas être mauvais, alors vous comptez ces coûts en entier; parfois, certains tests continuent de rendre le monde plus pauvre de 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 d'avion de ligne, vous vous retrouverez soit en faillite, soit en prison.

1
soru

C'est une question délicate.

À strictement parler, je dirais que ce n'est pas nécessaire. Il vaut mieux écrire des tests de niveau d'unité et de système de style BDD qui garantissent que les exigences commerciales 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 en premier lieu et si elle est nécessaire, ou s'il existe des exigences cachées dans le code qui ne sont pas reflétées dans votre documentation ou dans les user stories qui doit être codé dans un scénario de test de style BDD.

Personnellement, j'aime garder la couverture par ligne à environ 85-95% et les check-ins de porte à la ligne principale pour 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 sont suivies, cela donne beaucoup de couverture sans forcer les développeurs à perdre du temps à essayer de comprendre comment obtenir une couverture supplémentaire sur du code difficile à exercer ou un code trivial simplement pour le plaisir de la couverture.

0
Keith Brings

Votre réponse à cela dépend de votre philosophie (croyez-vous que c'est Chicago vs Londres? Je suis sûr que quelqu'un va le chercher). Le jury est toujours à ce sujet sur l'approche la plus efficace en termes de temps (car, après tout, c'est le plus grand facteur de réduction du temps passé sur les correctifs).

Certaines approches disent tester uniquement l'interface publique, d'autres disent tester 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 et voyez celle qui correspond le mieux à vos besoins.

0
anon