2011-06-30 116 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(); 

不需要额外的代码/没有条件代码/没有不同的链接。