Comment préconiser les tests unitaires sur du code privé?


15

J'essaie de préconiser les tests unitaires dans mon groupe de travail, mais une objection que j'obtiens souvent est qu'elle ne devrait être utilisée que pour les API exportées en externe (qui ne sont qu'une partie minimale et non critique de notre système), et non sur des applications internes et privées code (qui ne dispose désormais que de tests fonctionnels).

Bien que je pense que le test unitaire peut et doit être appliqué à tout le code, comment puis-je convaincre mes collègues?


3
Si vous avez des méthodes privées que vous ressentez le besoin de tester, c'est souvent un signe que votre code viole le SRP et qu'il y a une autre classe qui demande à être extraite et testée à part entière.
Paddyslacker

@Paddyslacker: Je pense que tout le code doit être testé. Je ne vois pas pourquoi une unité de code qui suit le principe de responsabilité unique ne devrait pas être soumise à des tests unitaires ...
Wizard79

4
@lorenzo, vous avez manqué mon point; peut-être que je ne l'ai pas très bien fait. Si vous extrayez ces méthodes privées dans une autre classe, elles devront désormais être accessibles à partir de votre classe d'origine. Les méthodes étant désormais publiques, elles devront être testées. Je n'impliquais pas qu'elles ne devraient pas être testées, j'impliquais que si vous ressentez le besoin de tester directement les méthodes, il est probable qu'elles ne devraient pas être privées.
Paddyslacker

@Paddyslacker: Je ressens le besoin de tester directement également des méthodes privées. Pourquoi pensez-vous qu'ils ne devraient pas être privés?
Wizard79

6
En testant des méthodes privées, vous brisez l'abstraction. Vous devez tester l'état et / ou le comportement, et non l'implémentation, dans les tests unitaires. Vos exemples / scénarios devraient être en mesure de vérifier le résultat du code privé - si vous trouvez cela difficile, alors, comme le dit Paddyslacker, cela pourrait signifier que vous violez SRP. Cela peut également signifier que vous n'avez pas distillé vos exemples pour être vraiment représentatif de ce que fait votre code.
FinnNk

Réponses:


9

Vos collègues peuvent confondre les vrais tests unitaires avec les tests d'intégration. Si votre produit est (ou possède) une API, les tests d'intégration peuvent être programmés en tant que cas de test NUnit. Certaines personnes croient à tort qu'il s'agit de tests unitaires.

Vous pouvez essayer de convaincre vos collègues avec ce qui suit (je suis sûr que vous connaissez déjà ce genre de choses, tout ce que je dis, c'est que le signaler à vos collègues pourrait aider):

  • Couverture de test . Mesurez le pourcentage de couverture de test réel de ces tests d'intégration. Il s'agit d'une vérification de la réalité pour ceux qui n'ont jamais exécuté de couverture de test. Parce qu'il est difficile d'exercer tous les chemins logiques lorsque l'entrée est à plusieurs couches, la couverture de test se situe entre 20% et 50%. Pour obtenir plus de couverture, vos collègues doivent écrire de vrais tests unitaires isolés.
  • Configuration . Déployez le même logiciel sous test et vous pourrez peut-être démontrer à vos collègues à quel point il est difficile d'exécuter leurs tests dans un environnement différent. Chemins vers divers fichiers, chaînes de connexion DB, URL de services distants, etc. - tout s'additionne.
  • Temps d'exécution . À moins que les tests ne soient de véritables tests unitaires et puissent s'exécuter en mémoire, leur exécution prendra beaucoup de temps.

12

Les raisons d'utiliser des tests unitaires sur du code interne / privé sont exactement les mêmes que pour les API prises en charge en externe:

  • Ils empêchent les bogues de se reproduire (les tests unitaires font partie de votre suite de tests de régression).
  • Ils documentent (dans un format exécutable!) Que le code fonctionne.
  • Ils fournissent une définition exécutable de ce que signifie "le code fonctionne".
  • Ils fournissent un moyen automatisé de démontrer que le code correspond bien aux spécifications (telles que définies par le point ci-dessus).
  • Ils montrent comment l'unité / classe / module / fonction / méthode échoue en présence d'une entrée inattendue.
  • Ils fournissent des exemples sur la façon d'utiliser l'unité, ce qui est une excellente documentation pour les nouveaux membres de l'équipe.

8

Si vous voulez dire privé comme je pense que vous le pensez, alors non - vous ne devriez pas le tester à l'unité. Vous ne devez tester en unités que les comportements / états observables. Il se peut que vous manquiez le point derrière le cycle "red-green-refactor" du TDD (et si vous ne faites pas de test en premier, le même principe s'applique). Une fois les tests écrits et réussis, vous ne voulez pas qu'ils changent pendant la refactorisation. Si vous êtes obligé de tester les fonctionnalités privées de manière unitaire, cela signifie probablement que les tests unitaires autour de la fonctionnalité publique sont défectueux. S'il est difficile et complexe d'écrire des tests autour du code public, alors votre classe en fait peut-être trop ou votre problème n'est pas clairement défini.

Pire encore, au fil du temps, vos tests unitaires deviendront une boule et une chaîne qui vous ralentiront sans ajouter de valeur (la modification de la mise en œuvre, par exemple l'optimisation ou la suppression de la duplication, ne devrait avoir aucun effet sur les tests unitaires). Le code interne doit cependant être testé unitaire car le comportement / l'état est observable (juste de manière restreinte).

Quand j'ai fait des tests unitaires pour la première fois, j'ai tiré toutes sortes de trucs pour tester des trucs privés, mais maintenant, avec quelques années à mon actif, je vois cela comme pire qu'une perte de temps.

Voici un exemple un peu idiot, bien sûr, dans la vraie vie, vous auriez plus de tests que ceux-ci:

Disons que vous avez une classe qui retourne une liste triée de chaînes - vous devez vérifier que le résultat est trié, pas comment il trie réellement cette liste. Vous pouvez démarrer votre implémentation avec un seul algorithme qui trie simplement la liste. Une fois cela fait, votre test n'a pas besoin de changer si vous changez ensuite votre algorithme de tri. À ce stade, vous avez un seul test (en supposant que le tri est intégré dans votre classe):

  1. Mon résultat est-il trié?

Supposons maintenant que vous vouliez deux algorithmes (peut-être que l'un est plus efficace dans certaines circonstances mais pas dans d'autres), alors chaque algorithme pourrait (et devrait généralement) être fourni par une classe différente et votre classe les choisit - vous pouvez vérifier que cela se produit pour vos scénarios choisis en utilisant des simulations, mais votre test d'origine est toujours valide et comme nous vérifions uniquement le comportement / l'état observable, il n'a pas besoin de changer. Vous vous retrouvez avec 3 tests:

  1. Mon résultat est-il trié?
  2. Étant donné un scénario (disons que la liste initiale est presque triée pour commencer), un appel est-il fait à la classe qui trie les chaînes à l'aide de l'algorithme X?
  3. Étant donné un scénario (la liste initiale est dans un ordre aléatoire), un appel est-il fait à la classe qui trie les chaînes en utilisant l'algorithme Y?

L'alternative aurait été de commencer à tester du code privé à l'intérieur de votre classe - vous n'y gagnez rien - les tests ci-dessus me disent tout ce que je dois savoir en ce qui concerne les tests unitaires. En ajoutant des tests privés, vous vous construisez une veste droite, combien de travail serait-ce si vous vérifiiez non seulement que le résultat était trié, mais aussi comment il était trié?

Les tests (de ce type) ne devraient changer que lorsque le comportement change, commencer à écrire des tests par rapport au code privé et cela sort par la fenêtre.


1
Il y a peut-être un malentendu sur le sens de «privé». Dans notre système, 99% du code est "privé", alors nous avons une petite API pour automatiser / contrôler à distance l'un des composants du système. Je veux dire un test d'unité du code de tous les autres modules.
Wizard79

4

voici une autre raison: dans le cas hypothétique, je devrais choisir entre le test unitaire de l'API externe et les parties privées, je choisirais les parties privées.

Si chaque partie privée est couverte par un test, l'API composée de ces parties privées doit également être couverte à près de 100%, à l'exception de la couche supérieure. Mais ce sera probablement une couche mince.

D'un autre côté, lorsque vous testez uniquement l'API, il peut être très difficile de couvrir entièrement tous les chemins de code possibles.


+1 "d'autre part ..." Mais si rien d'autre, ajoutez des tests où un échec serait le plus douloureux.
Tony Ennis

2

Il est difficile d'amener les gens à accepter les tests unitaires car cela semble être une perte de temps ("nous pourrions coder un autre projet rentable!") Ou récursif ("Et puis nous devons écrire des cas de test pour les cas de test!") Je suis coupable d'avoir dit les deux.

La première fois que vous trouvez un bug, vous devez faire face à la vérité que vous n'êtes pas parfait (à quelle vitesse nous les programmeurs oublions!) Et vous allez, "Hmmm."


Un autre aspect des tests unitaires est que le code doit être écrit pour pouvoir être testé. Se rendre compte que du code est facilement testable et pas du code fait qu'un bon programmeur va "Hmmm".


Avez-vous demandé à votre collègue pourquoi les tests unitaires n'étaient utiles que pour les API externes?


Une façon de montrer la valeur des tests unitaires consiste à attendre qu'un bogue se produise, puis à montrer comment les tests unitaires auraient pu l'empêcher. Cela ne veut pas le frotter au visage, c'est, dans leur esprit, déplacer les tests unitaires d'une théorie de la tour d'ivoire à une réalité dans les tranchées.

Une autre façon consiste à attendre que la même erreur se répète deux fois . "Uhhh, eh bien Boss, nous avons ajouté du code pour tester une valeur nulle après le problème de la semaine dernière, mais l'utilisateur a entré une chose vide cette fois!"


Mener par l'exemple. Écrivez des tests unitaires pour VOTRE code, puis montrez à votre patron la valeur. Ensuite, voyez si le patron appellera une pizza pour le déjeuner un jour et fera une présentation.


Enfin, je ne peux pas vous dire le soulagement que je ressens quand nous sommes sur le point de pousser pour produire et je reçois une barre verte des tests unitaires.


2

Il existe deux types de code privé: le code privé qui est appelé par le code public (ou le code privé qui est appelé par le code privé qui est appelé par le code public (ou ...)) et le code privé qui ne finit pas par être appelé par le public code.

Le premier est déjà testé à travers les tests du code public. Ce dernier ne peut pas être appelé du tout et doit donc être supprimé et non testé.

Notez que lorsque vous effectuez TDD, il est impossible qu'un code privé non testé existe.


Dans notre système, 99% du code est du troisième type : privé, non appelé par du code public et essentiel pour le système (seule une partie minimale de notre système possède une API externe publique).
Wizard79

1
"Notez que lorsque vous effectuez TDD, il est impossible qu'un code privé non testé existe." <- supprimer un cas de test, sans savoir que le test est le seul test à couvrir une branche particulière. OK, c'est plus du code "actuellement non testé", mais il est assez facile de voir une refactorisation triviale ultérieure changer ce code ... seule votre suite de tests ne le couvre plus.
Frank Shearar

2

Le test unitaire consiste à tester les unités de votre code. C'est à vous de définir ce qu'est une unité. Vos collègues définissent les unités comme des éléments API.

Quoi qu'il en soit, le test de l'API devrait également entraîner l'exercice de code privé. Si vous définissez la couverture du code comme un indicateur de la progression des tests unitaires, vous finirez par tester tout votre code. Si une partie du code n'a pas été atteinte, donnez à vos collègues trois choix:

  • définir un autre cas de test pour couvrir cette partie,
  • analyser le code pour justifier pourquoi il ne peut pas être couvert dans le contexte des tests unitaires mais devrait l'être dans d'autres situations,
  • supprimer le code mort qui n'a été ni couvert ni justifié.

Dans notre système, l'API n'est qu'une partie minimale, qui permet l'automatisation / le contrôle à distance pour une application tierce. Tester uniquement l'API représente une couverture de code de 1% ...
Wizard79
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.