web-dev-qa-db-fra.com

Comment faire des tests unitaires sur des membres privés (et méthodes) de classes C++

Je suis très nouveau dans les tests unitaires et je suis un peu confus.

J'essaie de faire des tests unitaires (à l'aide du framework de tests unitaires Boost) sur une classe C++ appelée VariableImpl. Voici les détails.

class Variable
{
public:
  void UpdateStatistics (void) {
    // compute mean based on m_val and update m_mean;
    OtherClass::SendData (m_mean);
    m_val.clear ();
  }
  virtual void RecordData (double) = 0;

protected:
  std::vector<double> m_val;

private:
  double m_mean;
};

class VariableImpl : public Variable
{
public:
  virtual void RecordData (double d) {
    // put data in m_val
  }
};

Ma question est comment puis-je vérifier que la moyenne est calculée correctement? Notez que 1) m_mean est protégé et 2) UpdateStatistics appelle une méthode d'une autre classe puis efface le vecteur.

La seule façon dont je peux voir serait d’ajouter un getter (par exemple, GetMean), mais je n’aime pas du tout cette solution et je ne pense pas qu’elle soit la plus élégante.

Comment dois-je faire?

Et que dois-je faire si je testais une méthode privée plutôt qu'une variable privée?

TIA,

Jir

25
Jir

Eh bien, les unités les tests devraient tester les unités et, idéalement, chaque classe est une unité autonome - cela découle directement du principe de responsabilité unique.

Tester les membres privés d’une classe ne devrait donc pas être nécessaire - la classe est une boîte noire qui peut être traitée telle quelle dans un test unitaire.

D’autre part, ce n’est pas toujours vrai et parfois pour de bonnes raisons (par exemple, plusieurs méthodes de la classe pourraient s’appuyer sur une fonction d’utilité privée qui devrait être testée). Une solution très simple, très cruelle mais finalement couronnée de succès consiste à insérer les éléments suivants dans votre fichier de test unitaire, avant , y compris l'en-tête qui définit votre classe:

#define private public

Bien sûr, cela détruit l’encapsulation et est mal . Mais pour les tests, cela sert à cela.

47
Konrad Rudolph

Pour une méthode/variable protégée, héritez d'une classe Test de la classe et effectuez vos tests.

Pour un cours privé, présentez une classe d'amis. Ce n'est pas la meilleure des solutions, mais peut faire le travail pour vous.

Ou ce bidouillage 

#define private public
11
DumbCoder

En général, je suis d’accord avec ce que d’autres ont dit ici: seule l’interface publique devrait faire l’objet d’un test unitaire. Néanmoins, je viens tout juste de rencontrer un cas dans lequel je devais d'abord appeler une méthode protégée pour me préparer à un test spécifique. J'ai d'abord essayé l'approche #define protected public mentionnée ci-dessus; cela fonctionnait avec Linux/gcc, mais échouait avec Windows/VisualStudio. La raison en était que le fait de changer protected en public a également changé le nom du symbole mutilé et m'a donc donné des erreurs de lieur: la bibliothèque a fourni une méthode protected __declspec(dllexport) void Foo::bar(), mais avec le #define en place, mon programme de test attendu a public __declspec(dllimport) void Foo::bar() méthode qui m'a donné une erreur de symbole non résolue.

Pour cette raison, je suis passé à une solution friend, en procédant comme suit dans mon en-tête de classe:

// This goes in Foo.h
namespace unit_test {   // Name this anything you like
struct FooTester; // Forward declaration for befriending
}

// Class to be tested
class Foo 
{
  ...
private:
  bool somePrivateMethod(int bar);
  // Unit test access
  friend struct ::unit_test::FooTester;
};

Et dans mon cas de test réel, j'ai fait ceci:

#include <Foo.h>
#include <boost/test/unit_test.hpp>
namespace unit_test {
// Static wrappers for private/protected methods
struct FooTester
{
  static bool somePrivateMethod(Foo& foo, int bar)
  {
    return foo.somePrivateMethod(bar);
  }
};
}

BOOST_AUTO_TEST_SUITE(FooTest);
BOOST_AUTO_TEST_CASE(TestSomePrivateMethod)
{
  // Just a silly example
  Foo foo;
  BOOST_CHECK_EQUAL(unit_test::FooTester::somePrivateMethod(foo, 42), true);
}
BOOST_AUTO_TEST_SUITE_END();

Cela fonctionne avec Linux/gcc ainsi que Windows/VisualStudio.

6
mindriot

Une bonne approche pour tester les données protégées en c ++ est l’affectation d’une classe proxy amie:

#define FRIEND_TEST(test_case_name, test_name)\
friend class test_case_name##_##test_name##_Test

class MyClass 
{
private:
  int MyMethod();
  FRIEND_TEST(MyClassTest, MyMethod);
};

class MyClassTest : public testing::Test 
{
public:
  // ...
  void Test1()
  {
    MyClass obj1;
    ASSERT_TRUE(obj1.MyMethod() == 0);
  }

  void Test2()
  {
    ASSERT_TRUE(obj2.MyMethod() == 0);
  }

  MyClass obj2;
};

TEST_F(MyClassTest, PrivateTests) 
{
 Test1();
 Test2(); 
}

voir plus de test goolge (gtest): http://code.google.com/p/googletest-translations/

3
photoscar

Bien que, à mon avis, le besoin de tester les membres privés/les méthodes d'une classe soit une odeur de code, je pense que cela est techniquement réalisable en C++.

Par exemple, supposons que vous ayez une classe Dog avec des membres/méthodes privés, à l'exception du constructeur public:

#include <iostream>
#include <string>

using namespace std;

class Dog {
  public:
    Dog(string name) { this->name = name; };

  private:
    string name;
    string bark() { return name + ": Woof!"; };
    static string Species;
    static int Legs() { return 4; };
};

string Dog::Species = "Canis familiaris";

Maintenant, pour une raison quelconque, vous souhaitez tester les solutions privées. Vous pouvez utiliser privablic pour y parvenir.

Incluez un en-tête nommé privablic.h avec l'implémentation souhaitée, comme ceci:

#include "privablic.h"
#include "dog.hpp"

puis mappez des talons selon les types de tout membre d'instance

struct Dog_name { typedef string (Dog::*type); };
template class private_member<Dog_name, &Dog::name>;

... et méthode d'instance;

struct Dog_bark { typedef string (Dog::*type)(); };
template class private_method<Dog_bark, &Dog::bark>;

faire la même chose avec tous les membres d'instance statiques 

struct Dog_Species { typedef string *type; };
template class private_member<Dog_Species, &Dog::Species>;

... et méthodes d'instance statique.

struct Dog_Legs { typedef int (*type)(); };
template class private_method<Dog_Legs, &Dog::Legs>;

Maintenant, vous pouvez tous les tester:

#include <assert.h>

int main()
{
    string name = "Fido";
    Dog fido = Dog(name);

    string fido_name = fido.*member<Dog_name>::value;
    assert (fido_name == name);

    string fido_bark = (&fido->*func<Dog_bark>::ptr)();
    string bark = "Fido: Woof!";
    assert( fido_bark == bark);

    string fido_species = *member<Dog_Species>::value;
    string species = "Canis familiaris";
    assert(fido_species == species);

    int fido_legs = (*func<Dog_Legs>::ptr)();
    int legs = 4;
    assert(fido_legs == legs);

    printf("all assertions passed\n");
};

Sortie:

$ ./main
all assertions passed

Vous pouvez regarder les sources de test_dog.cpp et dog.hpp .

DISCLAIMER: Grâce aux connaissances d'autres personnes intelligentes , j'ai assemblé la "bibliothèque" susmentionnée capable d'accéder aux membres privés et aux méthodes d'une classe C++ donnée sans modifier sa définition ou son comportement. Pour que cela fonctionne, il est (évidemment) nécessaire de connaître et d'inclure la mise en œuvre de la classe.

NOTE: J'ai révisé le contenu de cette réponse afin de suivre les directives suggérées par les relecteurs.

2
altamic

Test unitaire VariableImpl tel que si son comportement est garanti, Variable l’est également. 

Tester les composants internes n’est pas la pire chose au monde, mais l’objectif est qu’ils puissent être n'importe quoi tant que les contrats d’interface sont garantis. Si cela signifie créer un tas d'implémentations étranges et factices pour tester Variable, c'est raisonnable.

Si cela vous semble beaucoup, considérez que l'héritage d'implémentation ne crée pas une grande séparation des préoccupations. S'il est difficile d'effectuer des tests unitaires, il s'agit d'une odeur de code assez évidente pour moi.

1
Tom Kerr

Je suggère généralement de tester l'interface publique de vos classes, pas les implémentations privées/protégées. Dans ce cas, s'il ne peut pas être observé de l'extérieur par une méthode publique, le test unitaire peut ne pas avoir besoin de le tester.

Si la fonctionnalité nécessite une classe enfant, l'une ou l'autre teste la classe dérivée réelle OR et crée votre propre classe dérivée de test avec une implémentation appropriée.

1
Mark B

Exemple tiré du framework de test google:

// foo.h
#include "gtest/gtest_prod.h"
class Foo {
  ...
 private:
  FRIEND_TEST(FooTest, BarReturnsZeroOnNull);
  int Bar(void* x);
};

// foo_test.cc
...
TEST(FooTest, BarReturnsZeroOnNull) {
  Foo foo;
  EXPECT_EQ(0, foo.Bar(NULL));
  // Uses Foo's private member Bar().
}

L’idée principale est l’utilisation du mot clé friend cpp. Vous pouvez étendre cet exemple comme suit:

// foo.h
#ifdef TEST_FOO
#include "gtest/gtest_prod.h"
#endif

class Foo {
  ...
 private:
  #ifdef TEST_FOO
  FRIEND_TEST(FooTest, BarReturnsZeroOnNull);
  #endif
  int Bar(void* x);
};

Vous pouvez définir le préprocesseur TEST_FOO de deux manières:

1) dans le CMakeLists.txt

option(TEST "Run test ?" ON)
if (TEST)
  add_definitions(-DTEST_FOO)
endif()

2) comme arguments à votre compilateur

g++ -D TEST $your_args
1
user1823890