Вопрос по c++, unit-testing, private-members, private-methods – Как выполнить модульное тестирование на закрытых членах (и методах) классов C ++ [duplicate]

26

На этот вопрос уже есть ответ:

Как мне проверить приватную функцию или класс, у которого есть приватные методы, поля или внутренние классы? 47 ответов

Я очень плохо знаком с юнит-тестированием и немного растерялся.

Я пытаюсь выполнить модульное тестирование (используя среду модульного тестирования Boost) в классе C ++ с именем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

@ André На самом деле, довольно много сторонников TDD говорят, что инкапсуляция устарела и не должна использоваться. Бешенство. Этот пост идет в направлении, но останавливается, если не сказать прямо. В своем ответе (ниже) я привел пример использования частного метода это должно быть покрыто тестами вполне разумно. И тогда автор заявляет, что «тестируемость - это совершенно веская причина сделать что-то публичным» - нет, это не так. Если метод не должен использоваться потребителем класса (например, так как онне могу использовать осмысленно) это не должно быть публично. Konrad Rudolph
Посмотрите на некоторые ответы на аналогичный вопрос здесь: / Stackoverflow.com вопросы / 249847 / ... morechilli
Ну разве ты не видишь эффекты в OtherClass? R. Martinho Fernandes
Я хотел спросить что-то очень похожее. Но, по моему мнению, юнит-тестирование и большие классы просто плохо сочетаются. Konrad Rudolph

Ваш Ответ

8   ответов
11

проведите тестирование.

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

Или этот хак

#define private public
@ Конрад Рудольф - Да, именно поэтому я сказал, что это не лучшее решение. И я бы предположил, что для тестирования это не должно быть так уж сложно. DumbCoder
определение ключевых слов - это Неопределенное поведение: / Stackoverflow.com вопросы / 27778908 / ... bolov
Иметьfriend класс, на мой взгляд, слишком навязчи Konrad Rudolph
3

асса прокси-друга:

#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

2

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

В качестве примера предположим, что у вас есть класс 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.

ОТКАЗ: Благодаря пониманию других умные люди, Я собрал вышеупомянутую «библиотеку», способную обращаться к закрытым членам и методам данного класса C ++ без изменения его определения или поведения. Чтобы заставить его работать (очевидно), необходимо знать и включать реализацию класса.

НОТ: Я изменил содержание этого ответа, чтобы следовать указаниям рецензентов.

Пожалуйста, не размещайте какой-либо инструмент или библиотеку в качестве ответа. По крайней мере, продемонстрировать как это решает проблему в самом ответе. Baum mit Augen
47

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

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

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

#define private public

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

Разве дружба не будет более соответствовать стандарту, чем злые, и не сделает ли тестирование возможным с незначительной стоимостью инкапсуляции? paercebal
методы, которые до того, как какие-либо публичные / частные ключевые слова не станут общедоступными, кстати RiaD
@ Jir В твоем случае, неужели этого недостаточно для модульного тестированияOtherClass::SendData так как это вычисляет среднее? Konrad Rudolph
Кажется, этоdefine является наименее навязчивым решением. Я думаю, что проблемы, с которыми я сталкиваюсь, связаны с тем, что мои занятия не очень хорошо следуют принципу единой ответственности. Что касается примера игрушек, который я представил, не могли бы вы предложить мне создатьMean класс для использования в качестве частного членаVariable? СюдаMean можно проверить без проблем. Недостатком, однако, будет распространение классов. Jir
Хотя этот ответ работает на большинстве компиляторов, он не соответствует стандарту. Вы не можете использовать операторы #define, которые идентичны ключевым словам. См. C ++ 98: раздел 17.4.3.1.1 Alex
6

я согласен с тем, что здесь говорили другие - только общедоступный интерфейс должен тестироваться модульно. Тем не менее, у меня только что был случай, когда мне сначала нужно было вызвать защищенный метод, чтобы подготовиться к конкретному тестовому примеру. Я впервые попробовал#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.

1

что, если его поведение гарантировано, то и Variable.

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

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

1

н частные / защищенные реализации. В этом случае, если он не может быть обнаружен из внешнего мира общедоступным методом, тогда модульному тесту может не потребоваться его тестирование.

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

1

Пример из среды тестирования Google:

// foo..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().
}

Основная идея - использование Друг 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

Похожие вопросы