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 ce 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.
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 à mettre la responsabilité de 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 à quiconque 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 comme indiqué et B a besoin d'un accès spécial à A (les tests étant un bon exemple, j'ai également utilisé ce modèle lors de l'implémentation des liaisons boost python, parfois une fonction qui doit être privé en C++ est pratique à exposer dans la couche python pour l'implémentation).
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.
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.
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 (transformer efficacement les tests unitaires en dépendances client étroitement couplées, où vous devriez avoir non dépendances du tout).
vous pouvez ajouter une déclaration ami (classe ou fonction) à la classe testée.
vous pourriez ajouter #define private public
au début de vos fichiers de test, avant #include
- 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).
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 toujours être distinctes de l'API privée, ce qui vous laisse en quelque sorte un lien.
Voici un exemple pratique qui est du genre 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.
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. Il vous suffit ensuite 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.
Le test est 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 conduit à 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.