Qu’est-ce qui ne va pas avec un test unitaire d’un ami de la classe qu’il teste?

En C ++, j’ai souvent fait d’une classe de test un ami de la classe que je teste. Je le fais parce que je ressens parfois le besoin d’écrire un test unitaire pour une méthode privée, ou peut-être que je veux avoir access à un membre privé afin de pouvoir configurer plus facilement l’état de l’object pour pouvoir le tester. Pour moi, cela aide à préserver l’encapsulation et l’abstraction car je ne modifie pas l’interface publique ou protégée de la classe.

Si j’achète une bibliothèque tierce, je ne voudrais pas que son interface publique soit polluée par un tas de méthodes publiques que je n’ai pas besoin de connaître, simplement parce que le fournisseur voulait un test unitaire!

Je ne veux pas non plus avoir à me soucier d’une bande de membres protégés que je n’ai pas besoin de connaître si je hérite d’un cours.

C’est pourquoi je dis que cela préserve l’abstraction et l’encapsulation.

Lors de mon nouveau travail, ils ont déploré l’utilisation de classes d’amis, même pour les tests unitaires. Ils disent que la classe ne devrait rien “savoir” sur les tests et que vous ne voulez pas un couplage étroit de la classe et de son test.

Quelqu’un peut-il s’il vous plaît expliquer ces raisons plus pour que je puisse mieux comprendre? Je ne vois pas pourquoi utiliser un ami pour les tests unitaires est mauvais.

Idéalement, vous ne devriez pas avoir à tester les méthodes privées. Tout ce qu’un consommateur de votre classe devrait prendre en compte, c’est l’interface publique, c’est ce que vous devriez tester. Si une méthode privée a un bogue, elle devrait être interceptée par un test unitaire qui appelle une méthode publique sur la classe et qui finit par appeler la méthode privée boguée. Si un bug parvient à passer, cela indique que vos cas de test ne reflètent pas complètement le contrat que vous souhaitez que votre classe implémente. La solution à ce problème consiste presque certainement à tester les méthodes publiques avec plus de soin, sans que vos scénarios de test ne déterrent les détails d’implémentation de la classe.

Encore une fois, c’est le cas idéal. Dans le monde réel, les choses ne sont peut-être pas toujours aussi claires, et avoir une classe de test unitaire comme ami de la classe à laquelle il est soumis pourrait être acceptable, voire souhaitable. Pourtant, ce n’est probablement pas quelque chose que vous voulez faire tout le temps. Si cela semble assez souvent, cela pourrait signifier que vos cours sont trop volumineux et / ou que vous effectuez trop de tâches. Si tel est le cas, les subdiviser davantage en transformant des ensembles complexes de méthodes privées en classes distinctes devrait permettre de ne plus avoir besoin de tests unitaires sur les détails de l’implémentation.

Vous devez considérer qu’il existe différents styles et méthodes à tester: les tests de boîte noire ne testent que l’interface publique (en traitant la classe comme une boîte noire). Si vous avez une classe de base abstraite, vous pouvez même utiliser les mêmes tests pour toutes vos implémentations.

Si vous utilisez le test de la boîte blanche , vous pouvez même examiner les détails de l’implémentation. Non seulement sur les méthodes privées d’une classe, mais aussi sur les types d’instructions conditionnelles incluses (c’est-à-dire si vous souhaitez augmenter la couverture de vos conditions car vous savez que les conditions sont difficiles à coder). Dans les tests en boîte blanche, vous avez certainement un “couplage élevé” entre les classes / implémentation et les tests, car vous souhaitez tester l’implémentation et non l’interface.

Comme bcat l’a souligné, il est souvent utile d’utiliser la composition et des classes plus nombreuses mais plus petites au lieu de nombreuses méthodes privées. Cela simplifie le test des boîtes blanches car vous pouvez plus facilement spécifier les scénarios de test pour obtenir une bonne couverture de test.

Je pense que Bcat a donné une très bonne réponse, mais je voudrais exposer le cas exceptionnel auquel il fait allusion

Dans le monde réel, les choses ne sont peut-être pas toujours aussi claires, et avoir une classe de test unitaire comme ami de la classe à laquelle il est soumis pourrait être acceptable, voire souhaitable.

Je travaille dans une entreprise avec une base de code importante, qui pose deux problèmes qui consortingbuent tous deux à rendre un test unitaire souhaitable.

  • Nous souffrons de fonctions et de classes trop grandes qui nécessitent un refactoring, mais pour refactoriser, il est utile d’avoir des tests.
  • Une grande partie de notre code dépend de l’access à la firebase database, qui, pour diverses raisons, ne devrait pas être intégré aux tests unitaires.

Dans certains cas, Mocking est utile pour atténuer ce dernier problème, mais très souvent, cela conduit à une conception complexe (des hiérarchies de classes où aucune autre méthode ne serait nécessaire), tandis que l’on pourrait très simplement modifier le code de la manière suivante:

class Foo{ public: some_db_accessing_method(){ // some line(s) of code with db dependance. // a bunch of code which is the real meat of the function // maybe a little more db access. } } 

Maintenant, nous avons la situation où la fonction de la fonction doit être restructurée, nous aimerions donc un test unitaire. Il ne devrait pas être exposé publiquement. Maintenant, il y a une merveilleuse technique appelée moqueur qui pourrait être utilisée dans cette situation, mais le fait est que dans ce cas-ci un simulacre est excessif. Il me faudrait augmenter la complexité du design avec une hiérarchie inutile.

Une approche beaucoup plus pragmatique serait de faire quelque chose comme ceci:

  class Foo{ public: some_db_accessing_method(){ // db code as before unit_testable_meat(data_we_got_from_db); // maybe more db code. } private: unit_testable_meat(...); } 

Ce dernier me donne tous les avantages dont j’ai besoin pour les tests unitaires, notamment en me donnant ce précieux filet de sécurité pour détecter les erreurs produites lorsque je réorganise le code dans la viande. Pour pouvoir le tester, je dois associer une classe UnitTest, mais je dirais que c’est bien mieux qu’une hiérarchie de code inutile pour me permettre d’utiliser un Mock.

Je pense que cela devrait devenir un idiome, et je pense que c’est une solution pragmatique appropriée pour augmenter le retour sur investissement des tests unitaires.

Comme bcat a suggéré, dans la mesure du possible, vous devez trouver des bogues en utilisant l’interface publique elle-même. Mais si vous voulez faire des choses comme imprimer des variables privées et comparer avec les résultats attendus, etc. (pour aider les développeurs à déboguer facilement les problèmes), vous pouvez faire en sorte que la classe UnitTest soit un ami de la classe à tester. Mais vous devrez peut-être l’append sous une macro comme ci-dessous.

 class Myclass { #if defined(UNIT_TEST) friend class UnitTest; #endif }; 

Activer l’indicateur UNIT_TEST uniquement lorsque le test d’unité est requirejs. Pour les autres versions, vous devez désactiver cet indicateur.

Je ne vois rien de mal à utiliser une classe de test d’unité amie dans de nombreux cas. Oui, décomposer une grande classe en plus petites est parfois une meilleure solution. Je pense que les gens sont un peu trop hâtifs pour utiliser le mot-clé friend pour quelque chose comme ça – ce n’est peut-être pas une conception orientée object idéale, mais je peux sacrifier un peu d’idéalisme pour une meilleure couverture de test.

En règle générale, vous ne testez que l’interface publique afin que vous soyez libre de repenser et de restructurer l’implémentation. L’ajout de scénarios de test pour les membres privés définit une exigence et une ressortingction à l’implémentation de votre classe.

Faites en sorte que les fonctions que vous souhaitez tester soient protégées. Maintenant, dans votre fichier de test unitaire, créez une classe dérivée. Créez des fonctions d’encapsulation publiques qui appellent vos fonctions protégées classées sous test.