Test privé de méthode unitaire en c ++ à l'aide d'une classe d'amis

15

Je sais que c'est une pratique débattue, mais supposons que c'est la meilleure option pour moi. Je me demande quelle est la technique réelle pour ce faire. L'approche que je vois est la suivante:

1) Faites une classe d'amis celle de la classe dont je veux tester la méthode.

2) Dans la classe friend, créez une ou plusieurs méthodes publiques qui appellent la ou les méthodes privées de la classe testée.

3) Testez les méthodes publiques de la classe d'amis.

Voici un exemple simple pour illustrer les étapes ci-dessus:

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

Éditer:

Je vois que dans la discussion qui suit l'une des réponses, les gens s'interrogent sur ma base de code.

Ma classe a des méthodes qui sont appelées par d'autres méthodes; aucune de ces méthodes ne doit être appelée en dehors de la classe, elles doivent donc être privées. Bien sûr, ils pourraient être mis dans une seule méthode, mais logiquement, ils sont beaucoup mieux séparés. Ces méthodes sont suffisamment compliquées pour justifier des tests unitaires, et en raison de problèmes de performances, je devrai probablement recalculer ces méthodes, donc il serait bien d'avoir un test pour m'assurer que ma refactorisation n'a rien cassé. Je ne suis pas le seul à travailler dans l'équipe, même si je suis le seul à travailler sur ce projet incluant les tests.

Cela dit, ma question n'était pas de savoir si c'était une bonne pratique d'écrire des tests unitaires pour des méthodes privées, bien que j'apprécie les commentaires.

Akavall
la source
5
Voici une suggestion discutable pour une question discutable. Je n'aime pas le couplage d'un ami car le code publié doit être au courant du test. La réponse de Nir ci-dessous est un moyen d'y remédier, mais je n'aime toujours pas beaucoup changer la classe pour se conformer au test. Comme je ne me fie pas souvent à l'héritage, je fais parfois simplement protéger les méthodes autrement privées et j'ai une classe de test héritée et exposée au besoin. J'attends au moins trois "sifflements" pour ce commentaire, mais la réalité est que l'API publique et l'API de test peuvent différer et être distinctes de l'API privée. Meh.
J Trana
4
@JTrana: Pourquoi ne pas écrire cela comme une bonne réponse?
Bart van Ingen Schenau
D'accord. Ce n'est pas l'un de ceux dont vous vous sentez très fier, mais j'espère que cela vous aidera.
J Trana

Réponses:

23

Une alternative à un ami (enfin, dans un sens) que j'utilise fréquemment est un modèle que j'appelle access_by. C'est assez simple:

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

Supposons maintenant que la classe B participe au test A. Vous pouvez écrire ceci:

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

Vous pouvez ensuite utiliser cette spécialisation de access_by pour appeler des méthodes privées de A. En gros, cela revient à déclarer l'amitié dans le fichier d'en-tête de la classe qui souhaite appeler les méthodes privées de A. Il vous permet également d'ajouter des amis à A sans changer la source de A. Idiomatiquement, cela indique également à celui qui lit la source de A que A n'indique pas B un véritable ami dans le sens d'étendre son interface. Au contraire, l'interface de A est complète telle qu'elle est donnée et B a besoin d'un accès spécial à A (le test étant un bon exemple, j'ai également utilisé ce modèle lors de l'implémentation de liaisons boost python, parfois une fonction qui doit être privée en C ++ est pratique pour exposer dans la couche python pour l'implémentation).

Nir Friedman
la source
Curieux d'un cas d'utilisation valide pour faire le friend access_by, n'est-ce pas le premier non-ami suffisant - étant une structure imbriquée, il aurait accès à tout dans A? par exemple. coliru.stacked-crooked.com/a/663dd17ed2acd7a3
acidulé
10

Si c'est difficile à tester, c'est mal écrit

Si vous avez une classe avec des méthodes privées suffisamment complexes pour justifier leur propre test, la classe en fait trop. A l'intérieur, une autre classe essaie de sortir.

Extrayez les méthodes privées que vous souhaitez tester dans une nouvelle classe (ou des classes) et rendez-les publiques. Testez les nouvelles classes.

En plus de rendre le code plus facile à tester, cette refactorisation facilitera la compréhension et la maintenance du code.

Kevin Cline
la source
1
Je suis entièrement d'accord avec cette réponse, si vous ne pouvez pas tester complètement vos méthodes privées en testant les méthodes publiques, quelque chose ne va pas et refactoriser les méthodes privées dans leur propre classe serait une bonne solution.
David Perfors du
4
Dans ma base de code, une classe a une méthode très complexe qui initialise un graphe de calcul. Il appelle plusieurs sous-fonctions en séquence pour atteindre divers aspects de cela. Chaque sous-fonction est assez compliquée et la somme totale du code est très compliquée. Cependant, les sous-fonctions n'ont aucun sens si elles ne sont pas appelées dans cette classe et dans le bon ordre. Tout ce qui importe à l'utilisateur, c'est que le graphe de calcul soit complètement initialisé; les intermédiaires sont sans valeur pour l'utilisateur. S'il vous plaît, j'aimerais savoir comment je devrais refactoriser cela, et pourquoi cela a plus de sens que de simplement tester les méthodes privées.
Nir Friedman
1
@Nir: Faites le trivial: extrayez une classe avec toutes ces méthodes publiques, et faites de votre classe existante une façade autour de la nouvelle classe.
kevin cline
Cette réponse est correcte, mais cela dépend vraiment des données avec lesquelles vous travaillez.Dans mon cas, je ne dispose pas de données de test réelles, je dois donc en créer en observant des données en temps réel, puis les "injecter" dans mon application et voir comment Une seule donnée est trop complexe à gérer, il serait donc beaucoup plus facile de créer artificiellement des données de test partielles qui ne sont qu'un sous-ensemble des données en temps réel à cibler chacune que de reproduire réellement les données en temps réel. La fonction privée n'est pas complexe assez pour appeler à l'implémentation de plusieurs autres petites classes (avec les fonctionnalités respectives)
rbaleksandar
4

Vous ne devriez pas tester des méthodes privées. Période. Les classes qui utilisent votre classe ne se soucient que des méthodes qu'elle fournit, pas de celles qu'elle utilise sous le capot pour travailler.

Si vous vous inquiétez de la couverture de votre code, vous devez trouver des configurations qui vous permettent de tester cette méthode privée à partir de l'un des appels de méthode publique. Si vous ne pouvez pas faire cela, quel est l'intérêt d'avoir la méthode en premier lieu? C'est tout simplement du code inaccessible.

Ampt
la source
6
Le but des méthodes privées est de faciliter le développement (en séparant les préoccupations, ou en maintenant SEC, ou n'importe quel nombre de choses), mais elles sont censées être non permanentes. Ils sont privés pour cette raison. Ils peuvent apparaître, disparaître ou changer radicalement de fonctionnalités d'une implémentation à l'autre, c'est pourquoi les lier à des tests unitaires n'est pas toujours pratique, ni même utile.
Ampt
8
Ce n'est peut-être pas toujours pratique ou utile, mais c'est loin de dire que vous ne devriez jamais les tester. Vous parlez des méthodes privées comme si elles étaient les méthodes privées de quelqu'un d'autre; "ils pourraient apparaître, disparaître ...". Non, ils ne pouvaient pas. Si vous les testez directement, ce ne devrait être que parce que vous les entretenez vous-même. Si vous modifiez l'implémentation, vous modifiez les tests. En bref, votre déclaration générale est injustifiée. Bien qu'il soit bon d'avertir le PO à ce sujet, sa question est toujours justifiée et votre réponse n'y répond pas réellement.
Nir Friedman
2
Permettez-moi également de noter: le PO a déclaré d'emblée qu'il était conscient que c'était une pratique débattue. Donc, s'il veut le faire de toute façon, peut-être qu'il a vraiment de bonnes raisons pour cela? Aucun de nous ne connaît les détails de sa base de code. Dans le code avec lequel je travaille, nous avons des programmeurs très expérimentés et experts, et ils ont pensé qu'il était utile de tester des méthodes privées dans certains cas.
Nir Friedman
2
-1, une réponse comme "Vous ne devriez pas tester des méthodes privées." est à mon humble avis pas utile. Le sujet quand tester et quand ne pas tester les méthodes privées a été suffisamment discuté sur ce site. Le PO a montré qu'il était au courant de cette discussion et il est clair qu'il cherche des solutions sous l'hypothèse que dans son cas, tester des méthodes privées est la voie à suivre.
Doc Brown
3
Je pense que le problème ici est que cela peut être un problème XY très classique dans la mesure où OP pense qu'il a besoin de tester ses méthodes privées pour une raison ou une autre, alors qu'en réalité il pourrait aborder les tests d'un point de vue plus pragmatique, en regardant le privé méthodes comme de simples fonctions d'aide pour les publiques, qui sont le contrat avec les utilisateurs finaux de la classe.
Ampt
3

Il existe quelques options pour ce faire, mais gardez à l'esprit qu'ils modifient (essentiellement) l'interface publique de vos modules, pour vous donner accès aux détails de l'implémentation interne (transformant efficacement les tests unitaires en dépendances client étroitement couplées, où vous devriez avoir pas de dépendances du tout).

  • vous pouvez ajouter une déclaration ami (classe ou fonction) à la classe testée.

  • vous pouvez ajouter #define private publicau début de vos fichiers de test, avant #include-ing le code testé. Dans le cas où le code testé est une bibliothèque déjà compilée, cela pourrait faire que les en-têtes ne correspondent plus au code binaire déjà compilé (et provoquer UB).

  • vous pouvez insérer une macro dans votre classe testée et décider à une date ultérieure de sa signification (avec une définition différente pour tester le code). Cela vous permettrait de tester les composants internes, mais cela permettrait également au code client tiers de pirater votre classe (en créant leur propre définition dans la déclaration que vous ajoutez).

utnapistim
la source
2

Voici une suggestion discutable pour une question discutable. Je n'aime pas le couplage d'un ami car le code publié doit être au courant du test. La réponse de Nir est une façon de remédier à cela, mais je n'aime toujours pas beaucoup changer la classe pour se conformer au test.

Comme je ne me fie pas souvent à l'héritage, je fais parfois simplement protéger les méthodes autrement privées et j'ai une classe de test héritée et exposée au besoin. La réalité est que l'API publique et l'API de test peuvent différer et être distinctes de l'API privée, ce qui vous laisse en quelque sorte un lien.

Voici un exemple pratique pour lequel j'ai recours à cette astuce. J'écris du code intégré, et nous comptons un peu sur les machines à états. L'API externe n'a pas nécessairement besoin de connaître l'état de la machine d'état interne, mais le test devrait (sans doute) tester la conformité au diagramme de la machine d'état dans le document de conception. Je peux exposer un getter "état actuel" comme protégé, puis donner l'accès au test à cela, ce qui me permet de tester la machine d'état plus complètement. Je trouve souvent ce type de classe difficile à tester en tant que boîte noire.

J Trana
la source
Bien qu'il s'agisse d'une approche Java, il est assez standard de définir le niveau par défaut des fonctions privées au lieu de permettre aux autres classes du même package (les classes de test) de les voir.
0

Vous pouvez écrire votre code avec de nombreuses solutions de contournement pour vous éviter d'avoir à utiliser des amis.

Vous pouvez écrire des classes et ne jamais avoir de méthodes privées du tout. Il vous suffit alors de créer des fonctions d'implémentation au sein de l'unité de compilation, de laisser votre classe les appeler et de transmettre tous les membres de données auxquels ils ont besoin d'accéder.

Et oui, cela signifie que vous pouvez modifier les signatures ou ajouter de nouvelles méthodes de "mise en œuvre" sans changer votre en-tête à l'avenir.

Vous devez cependant peser si cela en vaut la peine. Et beaucoup dépendra vraiment de qui va voir votre en-tête.

Si j'utilise une bibliothèque tierce, je préfère ne pas voir les déclarations d'amis à leurs testeurs d'unité. Je ne veux pas non plus construire leur bibliothèque et faire exécuter leurs tests quand je le fais. Malheureusement, trop de bibliothèques open source tierces que j'ai construites le font.

Les tests sont le travail des rédacteurs de la bibliothèque, pas de ses utilisateurs.

Cependant, toutes les classes ne sont pas visibles pour l'utilisateur de votre bibliothèque. Beaucoup de classes sont des «implémentations» et vous les implémentez de la meilleure façon pour vous assurer qu'elles fonctionnent correctement. Dans ceux-ci, vous pouvez toujours avoir des méthodes et des membres privés, mais vous voulez que les testeurs unitaires les testent. Alors allez-y et faites-le de cette façon si cela aboutit à un code robuste plus rapide, facile à maintenir pour ceux qui en ont besoin.

Si les utilisateurs de votre classe font tous partie de votre propre entreprise ou équipe, vous pouvez également vous détendre un peu plus sur cette stratégie, en supposant qu'elle soit autorisée par les normes de codage de votre entreprise.

Vache à lait
la source