web-dev-qa-db-fra.com

Les espaces de noms anonymes rendent le code non testable

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'assistance match qui est utilisée par les méthodes de Foo, quelques constantes pour cette fonction d'assistance.

Et puis je veux écrire des tests.
Il serait parfaitement logique d'écrire un test unitaire séparé pour match, car il est assez simple.
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 si Foo est lourd et compliqué, un tel test n'isolera pas le candidat des autres facteurs indépendants.

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?

12
Abyx

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, vous abusez de friend, pour les espaces de noms sans nom, vous abusez de #include- 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 de mise en œuvre qui fuiront de toute façon, ou acceptez que votre test est inhabituellement éphémère.

7
Deduplicator

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.

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?

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. Il est alors temps de refactoriser un peu pour 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.

6
BЈовић

Il serait parfaitement logique d'écrire un test unitaire séparé pour la correspondance, car il n'est pas trivial.

Le code que vous avez montré pour match est un 1-liner assez trivial sans cas Edge délicats, ou est-ce comme un exemple simplifié? Quoi qu'il en soit, je suppose que c'est simplifié ...

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?

Cette question est ce qui voulait me faire sauter ici, car Deduplicator a déjà montré un très bon moyen de pénétrer et d'accéder à travers #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 même des tests unitaires n'est pas toujours de tester chaque petite micro-unité interne granulaire de fonctionnalité. La même question s'applique aux fonctions de portée de fichier statiques 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écessiterait les deuxfriendship et #include la ruse à la boîte blanche, la facilité de test des détails de mise en œuvre pour des temps de compilation améliorés.

Dans une sorte de perspective pragmatique, cela peut sembler grossier mais match peut ne pas être correctement implémenté avec certains cas Edge qui provoquent son déclenchement. Cependant, si la seule classe externe, Foo, qui a accès à match ne peut probablement pas l'utiliser d'une manière qui rencontre ces cas Edge, alors cela n'est pas pertinent pour l'exactitude de Foo que match a ces cas Edge qui ne seront jamais rencontrés à moins que Foo change, auquel cas les tests de Foo échoueront et nous le saurons immédiatement.

Un état d'esprit plus obsessionnel désireux de tester chaque détail de l'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), etc. Probablement, la testabilité des détails d'implémentation privés/internes n'est 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 une complexité inutile au monde extérieur.

5
user204677