2012-06-14 6 views
3

Существует тест класса, который в настоящее время принимает unique_ptr<Interface>&& в своем конструкторе, чтобы выразить, что он хочет взять одно владение реализацией интерфейса. Проблемы возникают, когда вы хотите протестировать этот класс, используя издеваемое Interface: mocking framework (HippoMocks) дает мне только Interface*, которого я не имею, поэтому не могу удалить.Единичное тестирование, mocking и unique_ptr

У меня была та же проблема, прежде чем при тестировании классов с const shared_ptr<Interface>& в качестве аргументов, но фиксировано что не Заказное не-оп Deleter:

template< class T > 
void NoDelete(T*) 
{ 
} 

    //create a shared_ptr without effective deleter 
template< class T > 
std::shared_ptr<T> mock_shared(T* t) 
{ 
    return std::shared_ptr<T>(t, NoDelete<T>); 
} 

Interface* iface = mocks.GetMeAMock<Interface>(); 
DoStuffWithSharedPtrOfInterface(mock_shared<Interface>(iface)); 

Аналогичное исправление unique_ptr реально не работает вне потому что deleter является аргументом шаблона:

template< class T > 
struct NoDelete 
{ 
    void operator()(T*) 
    { 
    } 
}; 

    //oops this is totally useless since std::unique_ptr< T, NoDelete<T> > 
    //is not quite the same type as std::unique_ptr<T> 
template< class T > 
std::unique_ptr< T, NoDelete<T> > mock_unique(T* t) 
{ 
    return std::unique_ptr< T, NoDelete<T> >(t, NoDelete<T>()); 
} 

Есть ли обходной путь для этого? Или я не должен использовать unique_ptr здесь, в первую очередь?

обновление Я дал это; должен работать, но sizeof (ptr) теперь 8, трудно сказать, какое влияние это имеет.

//use CustomUniquePtr::type instead of uniqe_ptr 
template< class T > 
struct CustomUniquePtr 
{ 
    typedef typename std::unique_ptr< T, void (*) (T*) > type; 
} 

    //use everywhere 
template< class T > 
CustomUniquePtr<T>::type make_unique(T* p) 
{ 
    return CustomUniquePtr<T>::type(p, Delete<T>); 
} 

    //use when mocking, doe not delete p! 
template< class T > 
CustomUniquePtr<T>::type mock_unique(T* p) 
{ 
    return CustomUniquePtr<T>::type(p, NoDelete<T>); 
} 
+1

«Чтобы выразить, что он хочет получить единоличное владение реализацией интерфейса». Для выражения этого выражения требуется значение 'unique_ptr', а не' && '. –

+0

«насмешливый фреймворк дает мне только интерфейс *, который у меня нет, поэтому его нельзя удалить». Можете ли вы не изменить насмешливую структуру, чтобы позволить вам это? Если ваш интерфейс требует владения, то ваша издевательская структура должна * проверить * право собственности. В противном случае вы его не тестируете. –

+1

@NicolBolas хорошая точка о стоимости, я оставлю это, так как это бесполезно для вопроса; указатели, возвращаемые насмешливыми фреймами, не указывают на что-либо, размещенное с использованием стандартного new/malloc/whatever, оно просто возится с vtable, так что это не то, что может принадлежать – stijn

ответ

2

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

MockRepository mocks; 
// create the mock 
std::unique_ptr<IFoo> foo(mocks.Mock<IFoo>()); 

// register the expectation for the destructor 
mocks.ExpectCallDestructor(foo.get()); 

// call to mocks destructor ok, mock not destroyed 
foo.reset(nullptr); 
+0

Этот ответ больше правильный, чем принятый ответ. –

+0

Это кажется предпочтительным ответом на меня, но это не сработало в моем случае w/shared_ptr. Я не исследовал почему. –

7

shared_ptr хранит его Deleter в куче вместе с другими данными бухгалтерского учета (RefCount и т.д.); unique_ptr не имеет накладных расходов кучи, поэтому дебетер должен быть сохранен в объекте и становится частью этого типа.

Вы можете создать шаблон конструктора на Deleter и преобразовать unique_ptr в shared_ptr, чтобы стереть тип дебелтора.

Лучше (в зависимости от размера интерфейса) было бы предоставить прокси-сервер Interface, который будет перенаправлен на насмешку Interface *.

+0

+1 не думал про прокси – stijn

2

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

  • В тестовом коде (и только в тестовом коде, вы не хотите, чтобы это в вашем приложении) специализироваться default_delete<Interface> к на самом деле ничего не удалять. Тогда это означает, что unique_ptr<Interface> никогда не удаляет объект, которому он владеет, что может быть нежелательно, если у вас есть объекты unique_ptr<Interface>, которые должны удалять принадлежащие ему объекты даже в тестах.

  • Создайте реализацию Interface, которая пересылает все экземпляру, предоставленному его конструктору. Затем вы могли бы динамически распределить экземпляр в тестах, которые переходят в интерфейс с интерфейсом mock-framework.

  • Измените структуру mock, чтобы динамически выделить интерфейс, чтобы его можно было удалить.

  • Смените свой код, чтобы использовать std::shared_ptr, чтобы вы могли сменить пользовательский дебетер. Тем не менее, это теряет свойство «уникальной собственности».

  • Измените свой код, чтобы использовать пользовательский интеллектуальный указатель, который удаляет или нет, в зависимости от параметров конструкции. Это может быть просто оберткой вокруг unique_ptr с флагом «delete or not».В вызовах назначения/уничтожения, если для флага установлено значение «не удалять», просто вызовите release() на обернутый unique_ptr, а не разрешая ему удалять объект. Пользовательский тип указателя имеет меньшее знакомство с пользователями, чем стандартный, и флаг будет занимать место.

  • Используйте генератор типа, как у вас, в «обновлении», поэтому везде говорят CustomUniquePtr<T>::type, а затем генератор типа добавляет дебетер. Недостатком здесь является то, что код пользователя должен знать об удалении, чтобы создавать новые экземпляры типа из необработанного указателя. Это означает, что пользовательский код не может легко создать реализацию вашего Interface (даже простой, которая просто регистрирует вызовы и пересылает их), не зная о дележе.

Там, конечно, могут быть другие варианты.

+0

+1 для подведения итогов всех опций – stijn

+0

...и + bounty для начала работы – stijn

+0

Лямбдой для случая shared_ptr будет: 'shared_ptr (Mocks.Mock (), [] (IFoo *) {});' Совместимо с VS 2010. –

0

Из всех вариантов, приведенных в ответах, наиболее подходящий вариант определенно модифицирует фреймворк, так как я могу просто продолжать использовать простой unique_ptr. Я не мог понять, как сделать это динамически распределять экземпляры макета, но, как оказалось, я могу заставить его высмеять delete. Это довольно взломанно, но похоже, что все работает нормально.

В основном гиппомоторы непосредственно изменяют vtable, делая записи указывают на функции, проверяющие ожидания и т. Д. Теперь, насколько я знаю в MSVC, первая запись в vtable для класса с виртуальным деструктором является скалярным удалением деструктора. Который вызывается при вызове delete ptr. И, как правило, генерируется компилятор и вызывает деструктор. Следовательно, изменяя vtable и делая первую точку входа на что-то другое, no-op, решает эту проблему.

Это код добавляется в MockRepository, это просто копия одного из RegisterExpect_ функций измененные ро всегда использовать void(void*) подпись:

template< int X > 
void NoOp(void*) 
{ 
} 

    //make first vtable entry point to NoOp 
template< int X, typename Z2 > 
void DeleteExpect(Z2 *mck) 
{ 
    const int funcIndex = 0; //index of scalar deleting destructor 
    void (MockRepository::*mfp)(void*) = &MockRepository::NoOp<X>; 
    BasicRegisterExpect(reinterpret_cast<mock<Z2> *>(mck), 
    funcIndex, reinterpret_cast<void (base_mock::*)()>(mfp), X); 
} 

#define MockDelete(obj) DeleteExpect<__COUNTER__>(obj) 

и вот как он используется:

MockRepository mocks; 
Interface* p = mocks.InterfaceMock<Interface>(); 
mocks.MockDelete(p); 

mocks.InterfaceMock<DataStitcher>(std::unique_ptr<Interface>(p)); 
Смежные вопросы