2012-06-14 182 views
3

有一個被測試的類,它目前在其構造函數中接受一個unique_ptr<Interface>&&,表示它想要對接口實現進行單一所有權。當想要使用模擬Interface來測試這個類時出現了問題:雖然模擬框架(HippoMocks)只給我提供了我不擁有的Interface*,因此不能刪除。單元測試,模擬和unique_ptr

我測試類以const shared_ptr<Interface>&作爲參數時之前同樣的問題,但固定的,通過提供一個自定義的無操作刪除器:

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

「嘲諷框架給我只有我不擁有的Interface *,因此不能刪除。」你不能改變嘲笑框架讓你擁有它嗎?如果你的界面需要所有權,那麼你的模擬框架需要*測試*所有權。否則,你不是真的在測試它。 –

+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將其刪除器與其他簿記數據(refcount等)一起存儲在堆上; unique_ptr沒有堆棧開銷,因此刪除器必須存儲在對象中併成爲類型的一部分。

您可以在Deleter上構造構造函數的模板並將unique_ptr轉換爲shared_ptr以擦除刪除器類型。

更好的(取決於接口的大小)將提供代理Interface對象,轉發給嘲笑的Interface *

+0

+1沒有想到代理 – stijn

2

我能想到的幾種選擇,沒有特定的順序:

  • 在測試代碼(僅測試代碼,你不希望這個在你的應用程序)專門default_delete<Interface>到沒有實際刪除任何東西這意味着unique_ptr<Interface>永遠不會刪除它擁有的對象,如果您有即使在測試中也應該刪除擁有對象的unique_ptr<Interface>對象,這可能並不理想。

  • 創建Interface的實現,該實現將所有內容都轉發給提供給其構造函數的實例。然後,您可以在轉發到模擬框架提供的接口的測試中動態分配一個實例。

  • 更改模擬框架以動態分配接口,以便將其刪除。

  • 將您的代碼更改爲使用std::shared_ptr,以便您可以傳遞自定義刪除程序。雖然這失去了「獨特的所有權」財產。

  • 更改您的代碼以使用刪除或不取決於構造參數的自定義智能指針。這可能只是一個圍繞unique_ptr的包裝,帶有「刪除或不刪除」標誌。如果標誌設置爲「不刪除」,則在分配/銷燬調用中,只需在包裝的unique_ptr上調用release(),而不是允許它刪除該對象。自定義指針類型與標準類型相比對用戶不太熟悉,並且該標誌將佔用空間。

  • 使用像你在「更新」中的類型生成器,所以無處不在說CustomUniquePtr<T>::type,然後讓類型生成器添加刪除器。這裏的缺點是用戶代碼必須知道刪除器從原始指針創建新類型的實例。這意味着用戶代碼無法輕鬆創建Interface(即使是簡單的記錄調用並轉發它們)的實現,也不知道刪除者。

當然可能有其他選擇。

+0

+1總結所有選項 – stijn

+0

...和+賞金讓我開始 – stijn

+0

一個lambda爲shared_ptr的情況是:'shared_ptr (Mocks.Mock (),[](IFoo *){});'兼容W/VS 2010. –

0

答案中給出的所有選項中,最合適的選項肯定是修改模擬框架,因爲我可以繼續使用普通的unique_ptr。我無法弄清楚如何使它動態分配模擬實例,但事實證明我可以使它自己模擬delete。這是很hacky,但它似乎確實工作。

基本上,hippomocks直接改變了vtable,使得條目指向檢查期望值的函數。現在,據我所知,MSVC中具有虛擬析構函數的類的第一個入口是標量刪除析構函數。調用delete ptr時會調用此函數。而且,這通常是編譯器生成的,並調用析構函數。因此,改變虛擬表並將第一個入口指向別的東西,一個無操作,解決了這個問題。

這是添加到MockRepository的代碼,它只是一個的RegisterExpect_功能之一的副本改變RO始終使用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));