Je souhaite effectuer des tests unitaires pour mon application C++.
Quelle est la forme correcte pour tester les membres privés d'une classe? Faire une classe d'amis qui testera les membres privés, utilisera une classe dérivée ou une autre astuce?
Quelle technique les API de test utilisent-elles?
Généralement, on ne teste que l'interface publique, comme indiqué dans les commentaires de la question.
Cependant, il est parfois utile de tester des méthodes privées ou protégées. Par exemple, la mise en œuvre peut avoir des complexités non triviales qui sont cachées aux utilisateurs et peuvent être testées plus précisément avec l'accès aux membres non publics. Il est souvent préférable de trouver un moyen de supprimer cette complexité ou de déterminer comment exposer publiquement les parties pertinentes, mais pas toujours.
Une des façons d’autoriser les tests unitaires à accéder aux membres non publics est via friend construct.
Répondre à cette question touche de nombreux autres sujets. En dehors de toute religiosité dans CleanCode, TDD et autres:
Il y a plusieurs façons d'accéder aux membres privés. Dans tous les cas, vous devez annuler le code testé! Ceci est possible aux deux niveaux d’analyse de C++ (préprocesseur et langage lui-même):
Tout définir au public
En utilisant le préprocesseur, vous pouvez rompre l’encapsulation.
#define private public
#define protected public
#define class struct
L’inconvénient est que la classe du code livré n’est pas la même que dans le test ! La norme C++ du chapitre 9.2.13 dit:
L'ordre d'allocation des membres de données non statiques avec différents le contrôle d'accès n'est pas spécifié.
Cela signifie que le compilateur a le droit de réorganiser les variables membres et les fonctions virtuelles pour le test. Vous pouvez vous battre pour que cela ne nuise pas à vos classes si aucun débordement de mémoire tampon ne se produit, mais cela signifie que vous ne testerez pas le même code que celui que vous livrez. Cela signifie que si vous accédez aux membres d'un objet initialisé par un code, compilé avec private
non défini sur public
, le décalage de votre membre peut être différent!
Copains
Cette méthode doit changer la classe testée pour la lier d'amitié avec la classe de test ou la fonction de test. Certains frameworks de test tels que gtest (FRIEND_TEST(..);
) ont des fonctionnalités spéciales pour supporter cette manière d'accéder aux choses privées.
class X
{
private:
friend class Test_X;
};
Il ouvre la classe uniquement pour le test et n'ouvre pas le monde, mais vous devez modifier le code qui est livré. À mon avis, c'est une mauvaise chose, car un test ne devrait jamais changer le code testé. Un autre inconvénient est que cela donne aux autres classes du code livré la possibilité d’introduire votre nom dans la classe en se nommant comme une classe de test (cela nuirait également à la règle ODR du standard C++).
Déclarer les choses privées protégées et dériver de la classe pour les tests
Pas très élégant, très intrusif, mais ça marche aussi:
class X
{
protected:
int myPrivate;
};
class Test_X: public X
{
// Now you can access the myPrivate member.
};
De toute autre manière avec des macros
Fonctionne, mais présente les mêmes inconvénients sur la conformité standard que la première méthode. par exemple.:
class X
{
#ifndef UNITTEST
private:
#endif
};
Je pense que les deux derniers moyens ne sont pas une alternative aux deux premiers, car ils ne présentent aucun avantage par rapport aux premiers, mais sont plus intrusifs pour le code testé. La première façon est très risquée, vous pouvez donc utiliser l'approche d'amitié.
Quelques mots sur la discussion jamais testée à titre privé. L'un des avantages des tests unitaires est que vous arriverez très tôt au point où vous devez améliorer la conception de votre code. C'est aussi parfois l'un des inconvénients des tests unitaires. Cela rend parfois l'orientation des objets plus compliquée que nécessaire. Surtout si vous suivez la règle pour concevoir des classes de la même manière que les objets du monde réel.
Ensuite, vous devez changer le code parfois en quelque chose de moche, parce que l'approche des tests unitaires vous y oblige. Travailler sur des cadres complexes, utilisés pour contrôler des processus physiques, en est un exemple. Là vous voulez mapper le code sur le processus physique, car souvent certaines parties du processus sont déjà très complexes. La liste de dépendance sur ces processus devient parfois très longue. C’est un moment possible, où tester des membres privés devient pratique. Vous devez faire un compromis entre les avantages et les inconvénients de chaque approche.
Les cours deviennent parfois complexes! Ensuite, vous devez décider de les séparer ou de les prendre tels qu’ils sont. Parfois, la deuxième décision a plus de sens. En fin de compte, il est toujours question des objectifs que vous souhaitez atteindre (par exemple, une conception parfaite, des temps d’incorporation rapides, des coûts de développement faibles, etc.).
Mon avis
Mon processus de décision pour accéder aux membres privés ressemble à ceci:
Je n'aime pas l'approche d'amitié, car elle modifie le code testé, mais le risque de tester quelque chose, qui peut ne pas être identique à celui livré (comme possible avec la première approche), ne justifiera pas le code plus propre.
BTW: Tester uniquement l'interface publique est également une tâche courante car, selon mon expérience, elle change aussi souvent que l'implémentation privée. Vous n'avez donc aucun avantage à réduire le test sur les membres du public.
Je n'ai pas trouvé de solution en or, mais vous pouvez utiliser friend
pour tester les membres privés, si vous savez comment le framework de test nomme ses méthodes. J'utilise ce qui suit pour tester les membres privés avec Google test. Bien que cela fonctionne assez bien, notez que c'est un hack, et je ne l'utilise pas dans le code de production.
Dans l'en-tête du code que je veux tester (stylesheet.h), j'ai:
#ifndef TEST_FRIENDS
#define TEST_FRIENDS
#endif
class Stylesheet {
TEST_FRIENDS;
public:
// ...
private:
// ...
};
et dans le test j'ai:
#include <gtest/gtest.h>
#define TEST_FRIENDS \
friend class StylesheetTest_ParseSingleClause_Test; \
friend class StylesheetTest_ParseMultipleClauses_Test;
#include "stylesheet.h"
TEST(StylesheetTest, ParseSingleClause) {
// can use private members of class Stylesheet here.
}
Vous ajoutez toujours une nouvelle ligne à TEST_FRIENDS si vous ajoutez un nouveau test qui accède aux membres privés. Les avantages de cette technique sont qu’elle n’est pas gênante dans le code testé, car vous n’ajoutez que quelques définitions, qui n’ont aucun effet si vous n’effectuez pas de test. L'inconvénient est que c'est un peu bavard dans les tests.
Maintenant, un mot quant à pourquoi vous voudriez faire cela. Idéalement, vous avez de petites classes avec des responsabilités bien définies et des interfaces faciles à tester. Cependant, dans la pratique, ce n'est pas toujours facile. Si vous écrivez une bibliothèque, ce qui est private
et public
est dicté par ce que vous voulez que le consommateur de la bibliothèque puisse utiliser (votre API publique) et non par ce qui nécessite des tests ou non. Vous pouvez avoir des invariants qui sont très peu susceptibles de changer et doivent être testés, mais qui ne présentent aucun intérêt pour le consommateur de votre API. Ensuite, le test de l'API en boîte noire ne suffit pas. De plus, si vous rencontrez des bugs et écrivez des tests supplémentaires pour éviter les régressions, il peut être nécessaire de tester des éléments private
.
Le désir de tester des membres privés est une odeur de design, ce qui indique généralement qu’une classe pris au piège dans votre classe a du mal à sortir. Toutes les fonctionnalités d'une classe doivent pouvoir être exercées via ses méthodes publiques. les fonctionnalités inaccessibles au public n'existent pas.
Il existe plusieurs approches pour vous rendre compte que vous devez vérifier que vos méthodes privées font ce qu’elles disent sur l’étain. Les classes d'amis sont les pires d'entre elles. ils associent le test à la mise en œuvre de la classe sous test d'une manière qui, à première vue, est fragile. L'injection de dépendances est un peu mieux adaptée: attribution des attributs de classe de dépendances des méthodes privées que le test peut fournir à des versions simulées afin de permettre le test de méthodes privées via l'interface publique. Le mieux est d'extraire une classe qui encapsule le comportement de vos méthodes privées en tant qu'interface publique, puis de tester la nouvelle classe comme vous le feriez normalement.
Pour plus de détails, consultez Clean Code .
Indépendamment des commentaires relatifs à la pertinence de tester des méthodes privées, supposons que vous ayez vraiment besoin de ... ceci est souvent le cas, par exemple, lorsque vous travaillez avec du code hérité avant de le reformater en quelque chose de plus approprié. Voici le motif que j'ai utilisé:
// In testable.hpp:
#if defined UNIT_TESTING
# define ACCESSIBLE_FROM_TESTS : public
# define CONCRETE virtual
#else
# define ACCESSIBLE_FROM_TESTS
# define CONCRETE
#endif
Ensuite, dans le code:
#include "testable.hpp"
class MyClass {
...
private ACCESSIBLE_FROM_TESTS:
int someTestablePrivateMethod(int param);
private:
// Stuff we don't want the unit tests to see...
int someNonTestablePrivateMethod();
class Impl;
boost::scoped_ptr<Impl> _impl;
}
Est-ce mieux que de définir des amis test? Cela semble moins bavard que l’alternative et ce qui se passe est clairement indiqué dans l’en-tête. Les deux solutions n'ont rien à voir avec la sécurité: si vous êtes vraiment préoccupé par les méthodes ou les membres, ceux-ci doivent être cachés dans une implémentation opaque, éventuellement avec d'autres protections.
Parfois, il est nécessaire de tester des méthodes privées. Le test peut être effectué en ajoutant FRIEND_TEST à la classe.
// Production code
// prod.h
#include "gtest/gtest_prod.h"
...
class ProdCode
{
private:
FRIEND_TEST(ProdTest, IsFooReturnZero);
int Foo(void* x);
};
//Test.cpp
// TestCode
...
TEST(ProdTest, IsFooReturnZero)
{
ProdCode ProdObj;
EXPECT_EQ(0, ProdObj.Foo(NULL)); //Testing private member function Foo()
}
Il existe une solution simple en C++ utilisant #define. Il suffit d’envelopper l’inclusion de votre "ClassUnderTest" comme ceci:
#define protected public
#define private public
#include <ClassUnderTest.hpp>
#undef protected
#undef private
[Le crédit va à cet article et RonFox] [1]