2011-06-30 111 views
9

我有一個幾乎單值般​​類一樣的人:嘲笑靜態/全局函數的最簡單方法?

class Person 
{ 
public: 
    Person(ThirdPartyClass *object); 
    virtual ~Person(void); 

    virtual std::string GetFullName() const; 
    virtual int GetAge() const; 
    virtual int GetNumberOfDaysTillBirthday() const; 
}; 

我使用的是第三方庫和ThirdPartyClass需要有一個名爲Destroy(第三方庫的一部分)的全局/靜態函數呼籲它摧毀它。這個Destroy函數在Person析構函數中被調用。

現在我試圖單元測試我的人類,我需要一種方法來模擬/存根Destroy方法。我想我可以在靜態Destroy函數中編寫一個包裝類,然後使用依賴注入將這個包裝注入到Person類中,但似乎只是在這個簡單的類上調用這個函數而已。什麼是簡單直接的方法來做到這一點?或者依賴注入真的是最好的方式嗎?

更新

最終,我決定去創造一類包裝的所有第三方庫的全局函數,然後使用依賴注入到這個類傳遞到我的個人類的構造函數。這樣我可以將Destroy方法留存。雖然person類只使用一個函數,但是庫中的其他函數在我的代碼中的其他位置被調用,並且因爲我需要測試那些我將面對同一問題的函數。

我在我的主應用程序代碼中創建了這個包裝類的單個實例,並在需要時注入它。我選擇走這條路,因爲我認爲它更清晰。我喜歡Billy ONeal的解決方案,我認爲它解決了我的問題,但是我意識到如果我將代碼保留幾個月,然後再回過頭來看看與依賴注入相比,發生什麼事情需要更長的時間。我想起了蟒蛇格言的禪意:「顯性比隱性更好」。我感覺依賴注入使得發生的事情更加明確。

+0

剛剛創建靜態/全局函數作爲存根並調用它有什麼問題? – littleadv

+0

@littleadv:嗯,我剛剛進入單元測試,但我的理解是你不想修改你正在測試的類來測試它。所以,如果我正確理解你,通過創建一個存根Destroy方法並在我的Person類中使用它,我正在更改我的人員類,然後我必須以某種方式在測試版本和生產版本之間切換。 – User

+0

@用戶 - 不要修改您正在測試的類,而要將自己的'ThirdPartyClass'實現爲存根。 – littleadv

回答

8

創建鏈接縫。把你的頭摧毀聲明,然後有兩個實現文件:

// Destroy.cpp 
void Destroy() 
{ 
    //Code that really does destruction 
} 

以及測試:

// DestroyFake.cpp 
void Destroy() 
{ 
    //Code that does fake destruction 
} 

,第一個文件鏈接到你的主二進制文件,第二個文件到您的測試二進制文件。

除此之外,你所要求的是不可能的。鏈接器烘焙調用全局函數和靜態方法,直接跳轉到被調用代碼 - 沒有查找或決定過程掛鉤創建任何類型的過載,如你正在尋找的(超越Destroy調用更多輕鬆嘲笑)。

+0

你的意思是爲兩個版本的Destroy創建兩個單獨的靜態lib項目?這就是我所知道的如何鏈接..或者你有不同的想法來連接文件? – User

+0

@User:將通用代碼放在靜態庫中。將第一個放在主EXE項目中,第二個放入您的測試EXE項目中。不需要將這兩個分爲靜態庫。 –

+0

然後Destroy頭文件去哪裏? – User

0

我只是在這裏打球,但一個途徑,爲你的公司可能是提供自己的destroy功能,並通過添加Third_Party_Lib指針周圍的包裝類歧義贊成這樣的電話...

#include <iostream> 

namespace Third_Party_Lib 
{ 
    struct X { }; 
    void destroy(X*) { std::cout << "Third_Party_Lib::destroy()\n"; } 
} 

template <typename T> 
struct Wrap 
{ 
    Wrap(const T& t) : t_(t) { } 
    operator T&() { return t_; } 
    operator const T&() const { return t_; } 
    T t_; 
}; 

namespace Mine 
{ 

#if TEST_MODE 
    // this destroy will be called because it's a better match 
    // not needing Wrap::operator T&... 
    void destroy(Wrap<Third_Party_Lib::X*>) { std::cout << "Mine::destroy()\n"; } 
#endif 

    struct Q 
    { 
     Q(Third_Party_Lib::X* p) : p_(p) { } 
     ~Q() { destroy(Wrap<Third_Party_Lib::X*>(p_)); } 
     Third_Party_Lib::X* p_; 
    }; 
} 

#if TEST_MODE  
int main() 
{ 
    Mine::Q q(new Third_Party_Lib::X); 
} 
#endif 
0

函數指針會創建替代另一個實現的方法。此外,它對呼叫者來說是透明的(除非他們正在做一些非常愚蠢的事情,比如撥打sizeof(funcname))。

2

簡單,使用Typemock Isolator++(免責聲明 - 我在那裏工作)

添加1號線在您的測試僞造/嘲笑Destroy

全球使用:

FAKE_GLOBAL(Destroy); 

公共靜態使用:

WHEN_CALLED(Third_Party::Destroy()).Ignore(); 

私人靜力學使用:

PRIVATE_WHEN_CALLED(Third_Party::Destroy).Ignore(); 

不需要額外的代碼/沒有條件代碼/沒有不同的鏈接。