Voici un code C ++ typique:
foo.hpp
#pragma once
class Foo {
public:
void f();
void g();
...
};
foo.cpp
#include "foo.hpp"
namespace {
const int kUpperX = 111;
const int kAlternativeX = 222;
bool match(int x) {
return x < kUpperX || x == kAlternativeX;
}
} // namespace
void Foo::f() {
...
if (match(x)) return;
...
Il ressemble à un code C ++ idiomatique décent - une classe, une fonction d'aide match
qui est utilisée par les méthodes de Foo
, quelques constantes pour cette fonction d'aide.
Et puis je veux écrire des tests.
Il serait parfaitement logique d'écrire un test unitaire séparé match
, car il n'est pas banal.
Mais il réside dans un espace de noms anonyme.
Bien sûr, je peux écrire un test qui appellerait Foo::f()
. Cependant, ce ne sera pas un bon test s'il Foo
est lourd et compliqué, un tel test n'isolera pas le candidat des autres facteurs non liés.
Je dois donc déplacer match
et tout le reste hors de l'espace de noms anonyme.
Question: quel est l'intérêt de mettre des fonctions et des constantes dans l'espace de noms anonyme, si cela les rend inutilisables dans les tests?
la source
foo.cpp
, pas l'en-tête! OP semble comprendre très bien que vous ne devriez pas mettre des espaces de noms dans un en-tête.friend
n'est pas recommandé d' abuser du mot - clé à cette fin.Combinez cela avec votre hypothèse que si une restriction pour une méthode conduit à une situation où vous ne pouvez plus la tester directement, cela impliquerait que les méthodes privées n'étaient pas utiles.Réponses:
Si vous souhaitez tester de manière unitaire les détails d'implémentation privés, vous effectuez le même type d'esquive pour les espaces de noms sans nom que pour les membres de classe privés (ou protégés):
Entrez et faites la fête.
Alors que pour les classes que vous abusez
friend
, pour les espaces de noms sans nom, vous#include
abusez du mécanisme, qui ne vous force même pas à changer le code.Maintenant que votre code de test (ou mieux seulement quelque chose pour tout exposer) est dans le même TU, il n'y a plus de problème.
Un mot d'avertissement: si vous testez les détails de l'implémentation, votre test sera interrompu si ceux-ci changent. Assurez-vous vraiment de ne tester que les détails d'implémentation qui fuiront de toute façon, ou acceptez que votre test est inhabituellement éphémère.
la source
La fonction dans votre exemple semble assez complexe, et il peut être préférable de la déplacer vers l'en-tête, à des fins de test unitaire.
Pour les isoler du reste du monde. Et ce ne sont pas seulement les fonctions et les constantes que vous pouvez mettre dans l'espace de noms anonyme - c'est aussi pour les types.
Cependant, si cela rend vos tests unitaires très complexes, vous vous trompez. Dans ce cas, la fonction n'y appartient pas. Ensuite, il est temps pour une petite refactorisation de simplifier les tests.
Ainsi, dans un espace de noms anonyme, seules les fonctions très simples, parfois les constantes et les types (y compris les typedefs) utilisés dans cette unité de traduction, doivent être utilisées.
la source
Le code que vous avez montré
match
est un 1-liner assez trivial sans cas de bord délicat, ou est-ce comme un exemple simplifié? Quoi qu'il en soit, je suppose que c'est simplifié ...Cette question est ce qui voulait me faire sauter ici, car Deduplicator a déjà montré un excellent moyen de pénétrer et d'accéder à la
#include
ruse. Mais le libellé ici donne l'impression que tester chaque détail d'implémentation interne de tout est une sorte d'objectif final universel, quand il est loin de là.L'objectif d'un test unitaire uniforme n'est pas toujours de tester chaque petite micro-unité interne granulaire de fonctionnalité. La même question s'applique aux fonctions statiques de portée de fichier en C. Vous pouvez même rendre la question plus difficile à répondre en demandant pourquoi les développeurs utilisent
pimpls
en C ++ qui nécessiteraient les deuxfriendship
et la#include
ruse à la boîte blanche, échangeant une testabilité facile des détails d'implémentation pour des temps de compilation améliorés, par exempleDans une sorte de perspective pragmatique, cela peut sembler grossier mais
match
peut ne pas être correctement mis en œuvre avec certains cas de bord qui le font se déclencher. Cependant, si la seule classe externeFoo
, qui a accès à,match
ne peut pas l'utiliser d'une manière qui rencontre ces cas marginaux, alors c'est peu pertinent pour l'exactitude deFoo
celamatch
a ces cas limites qui ne seront jamais rencontrés à moins deFoo
changements, à quel point les testsFoo
échoueront et nous le saurons immédiatement.Un état d'esprit plus obsessionnel désireux de tester chaque détail d'implémentation interne (peut-être un logiciel essentiel à la mission, par exemple) pourrait vouloir s'introduire et faire la fête, mais beaucoup de gens ne pensent pas nécessairement que c'est la meilleure idée, car cela créerait le tests les plus fragiles imaginables. YMMV. Mais je voulais juste aborder le libellé de cette question qui donne l'impression que ce genre de testabilité au niveau de détail interne ultra fin devrait être un objectif final, alors que même l'état d'esprit des tests unitaires les plus rigoureux pourrait se détendre un peu ici et éviter de radiographier les internes de chaque classe.
Alors pourquoi les gens définissent-ils des fonctions dans des espaces de noms anonymes en C ++ ou en tant que fonctions statiques de portée de fichier avec une liaison interne en C, cachées du monde extérieur? Et c'est surtout ça: les cacher du monde extérieur. Cela a un certain nombre d'effets, de la réduction des temps de compilation à la réduction de la complexité (ce qui n'est pas accessible ailleurs ne peut pas causer de problèmes ailleurs) et ainsi de suite. La testabilité des détails d'implémentation privés / internes n'est probablement pas la chose la plus importante dans l'esprit des gens lorsqu'ils le font, par exemple, en réduisant les temps de construction et en cachant la complexité inutile du monde extérieur.
la source