Подтвердить что ты не робот

Как выполнить модульное тестирование для частных членов (и методов) классов С++

Я очень новичок в модульном тестировании, и я немного смущен.

Я пытаюсь выполнить модульное тестирование (используя платформу тестирования модулей Boost) на классе С++ под названием VariableImpl. Вот подробности.

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
  }
};

Мой вопрос в том, как проверить, правильно ли вычисляется среднее значение? Обратите внимание, что 1) m_mean защищен и 2) UpdateStatistics вызывает метод другого класса, а затем очищает вектор.

Единственный способ, которым я могу видеть, - добавить геттер (например, GetMean), но мне совсем не нравится это решение, и я не думаю, что это самый элегантный.

Как мне сделать?

И что мне делать, если я должен проверить частный метод вместо частной переменной?

ТИА,

Jir

4b9b3361

Ответ 1

Ну, модульное тестирование должно тестировать единицы измерения, и в идеале каждый класс является автономной единицей - это следует непосредственно из принципа единой ответственности.

Поэтому тестирование частных членов класса не должно быть необходимым - класс - это черный ящик, который может быть включен в unit test как есть.

С другой стороны, это не всегда верно, а иногда и с серьезными причинами (например, несколько методов класса могут полагаться на частную служебную функцию, которая должна быть проверена). Одно очень простое, очень крутое, но в конечном итоге успешное решение заключается в том, чтобы поместить следующее в ваш тестовый файл, прежде чем включать заголовок, который определяет ваш класс:

#define private public

Конечно, это разрушает инкапсуляцию и зло. Но для тестирования он служит цели.

Ответ 2

Для защищенного метода/переменной наследуйте класс Test из класса и выполняйте тестирование.

Для частного введите класс друзей. Это не лучшее решение, но может сделать для вас работу.

Или этот хак

#define private public

Ответ 3

В общем, я согласен с тем, что говорили другие, - только публичный интерфейс должен быть проверен подразделением. Тем не менее, у меня только что был случай, когда я сначала должен был вызвать защищенный метод, чтобы подготовиться к конкретному тестовому примеру. Сначала я попробовал подход #define protected public, упомянутый выше; это работало с Linux/gcc, но не с Windows/VisualStudio. Причина заключалась в том, что изменение protected на public также изменило измененное имя символа и, таким образом, дало мне ошибки компоновщика: библиотека предоставила защищенный метод __declspec(dllexport) void Foo::bar(), но с #define на месте, моя тестовая программа ожидала публичного __declspec(dllimport) void Foo::bar(), который дал мне нерешенную ошибку символа.

По этой причине я переключился на решение на основе friend, выполнив следующее в своем классе:

// 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;
};

И в моем фактическом тестовом случае я сделал это:

#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();

Это работает с Linux/gcc, а также с Windows/VisualStudio.

Ответ 4

Хороший подход для проверки защищенных данных в С++ - это назначение класса прокси-класса друга:

#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(); 
}

см. больше теста на голой (gtest): http://code.google.com/p/googletest-translations/

Ответ 5

Unit test VariableImpl такой, что если его поведение будет гарантировано, то переменная.

Тестирование внутренних компонентов - это не самая худшая вещь в мире, но цель состоит в том, что они могут быть чем угодно, если гарантируются интерфейсные контракты. Если это означает создание кучи странных макетных реализаций для проверки Variable, тогда это разумно.

Если это похоже на много, подумайте, что наследование реализации не создает большого разделения проблем. Если это трудно для unit test, то это довольно очевидный запах кода для меня.

Ответ 6

Обычно я предлагаю тестировать открытый интерфейс ваших классов, а не частные/защищенные реализации. В этом случае, если он не может быть обнаружен из внешнего мира с помощью общедоступного метода, тогда unit test может не потребоваться его проверить.

Если для функциональности требуется дочерний класс, либо unit test реальный производный класс ИЛИ создайте собственный тестовый производный класс, который имеет соответствующую реализацию.

Ответ 7

Хотя, на мой взгляд, необходимость тестирования частных членов/методов класса является запахом кода, я думаю, что это технически возможно на С++.

В качестве примера предположим, что у вас есть класс Dog с частными членами/методами, за исключением открытого конструктора:

#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";

Теперь по какой-то причине вы хотели бы протестировать частные. Вы можете использовать privablic для достижения этого.

Включите заголовок с именем privablic.h вместе с желаемой реализацией:

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

затем сопоставьте некоторые заглушки в соответствии с типами любого члена экземпляра

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

... и метод экземпляра;

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

сделать то же самое со всеми статическими членами экземпляра

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

... и статические методы экземпляра.

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

Теперь вы можете проверить их все:

#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");
};

Вывод:

$ ./main
all assertions passed

Вы можете посмотреть источники test_dog.cpp и dog.hpp.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Благодаря пониманию других умных людей, я собрал вышеупомянутую "библиотеку", имеющую доступ к закрытым членам и методам данный класс С++ без изменения его определения или поведения. Чтобы заставить его работать, он (очевидно) должен знать и включать реализацию класса.

ПРИМЕЧАНИЕ. Я пересмотрел содержание этого ответа, чтобы следовать директивам, предложенным рецензентами.

Ответ 8

Пример из рамок тестирования 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 private member Bar().
}

Основная идея заключается в использовании ключевого слова друга cpp. Вы можете расширить этот пример следующим образом:

// 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);
};

Вы можете определить препроцессор TEST_FOO двумя способами:

1) в CMakeLists.txt

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

2) в качестве аргументов для вашего компилятора

g++ -D TEST $your_args