2011-07-21 4 views
24

Я очень новичок в тестировании модулей, и я немного смущен.Как сделать модульное тестирование для частных членов (и методов) классов C++

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

+0

Я хотел спросить что-то очень похожее. Но, на мой взгляд, модульное тестирование и большие классы просто плохо смешиваются. –

+0

Ну, разве вы не видите эффекты в OtherClass? –

+0

Вы должны прочитать [Враги разработки, основанной на тестировании, часть I: инкапсуляция] (http://jasonmbaker.wordpress.com/2009/01/08/enemies-of-test-driven-development-part-i-encapsulation/). –

ответ

46

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

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

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

#define private public 

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

+1

Похоже, что это 'define' является наименее навязчивым решением. Я думаю, что проблемы, с которыми я сталкиваюсь, связаны с тем, что мои классы не очень хорошо следуют принципу единой ответственности. Что касается примера игрушек, который я представил, предложите ли вы создать класс 'Mean', который будет использоваться как частный член' Variable'? Таким образом, «Mean» может быть протестирован без каких-либо проблем. Однако недостатком будет распространение классов. – Jir

+0

@Jir В вашем случае не должно быть достаточно, чтобы выполнить unit-test 'OtherClass :: SendData', поскольку это вычисляет среднее значение? –

+0

Собственно, да. Я думаю, что я начинаю получать основы модульного тестирования ... спасибо! – Jir

9

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

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

Или этот хак

#define private public 
+5

На мой взгляд, класс 'friend' слишком навязчив. –

+0

@ Konrad Rudolph - Да, поэтому я сказал, что это не лучшие решения. И я бы предположил, что для тестирования это не должно быть большой сделки. – DumbCoder

+1

определяющие ключевые слова ** Неопределенное поведение **: http://stackoverflow.com/questions/27778908/define-private-to-public-in-c – bolov

1

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

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

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

1

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

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

3

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

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

видеть больше Goolge тест (GTEST): http://code.google.com/p/googletest-translations/

4

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

0

Example from the google testing framework:

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

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

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

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

#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.

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

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

+1

Пожалуйста, не просто отправляйте какой-либо инструмент или библиотеку в качестве ответа. По крайней мере, продемонстрируйте [как он решает проблему] (http://meta.stackoverflow.com/a/251605) в самом ответе. –