考虑以下代码:
class SomeInterface
{
public:
virtual void foo() = 0;
virtual ~SomeInterface() {}
};
class RealImplementation : public SomeInterface
{
public:
void foo() { /* do complete stuff */}
};
class MockImplementation : public SomeInterface
{
public:
void foo() { /* do simple stuff */ }
};
更具体地说,有一些例子:
class IInjector
{
public:
virtual bool injectDLL() const = 0;
virtual ~IInjector() {}
};
class RealInjector : public IInjector
{
public:
bool injectDLL() const
{
int pid = GetHookedProcessId();
char name = readDllNameFromSomewhere();
if (loadDllInSomeProcess(pid, name))
return true;
else
return false;
}
};
class Hook
{
public:
bool hookProcess(const IInjector& injector)
{
return injector.injectDLL();
}
};
然后在测试代码中,通常会执行以下操作:
class MockInjector : public IInjector {
public:
MOCK_METHOD0(injectDLL, bool());
};
TEST(HookTest, CanHookSomething) {
MockInjector injector;
EXPECT_CALL(injector, injectDLL()).Times(1);
Hook hook;
EXPECT_TRUE(hook.hookProcess(injector));
}
要验证是否调用了injectDLL
方法,我们必须知道hookProcess
被称为injectDLL
。但这是方法hookProcess
的实现细节。因此,在我们的测试中,我们打开了一些实现细节。但这是很常见的情况。因此,使用模拟程序时可以打开一些实现细节吗?
答案 0 :(得分:1)
单元测试通常作为白盒测试技术应用-您知道被测试的代码。否则,您将也无法发表有关测试所达到的代码覆盖率的声明。并且,单元测试代码被视为属于该代码,并与代码等一起置于版本控制之下。因此,对于您的单元测试,您无需“披露”实现细节-它们也不会被隐藏。 >
但是,可以肯定的是,随着模拟的进行,您的测试将更加依赖于实现细节,这意味着它们更有可能中断或需要维护。但这只是您在这种情况下可能必须接受的权衡。
答案 1 :(得分:0)
如果Hook::injectProcess
的功能规范是使用用户提供的IInjector
实例来注入DLL,则injectProcess
的任何实现都不会调用injectDLL
方法该用户提供的实例上的对象肯定损坏了。因此,在您提供的示例案例中,封装似乎丝毫没有中断。
您显示的示例是一个很好的依赖注入示例,非常适合使用Google Test等框架进行测试。它允许通过使用用户可构造的模拟对象调用公共函数来模拟在公共接口中形式化的某些特定操作。
在一般情况下,这样的代码可能会破坏封装。认为被测函数不是hookProcess
方法,其明确指定的工作是要注入DLL,因此您可以肯定必须注入DLL。您可能想测试一个应该获取一些数据的函数,并且当前它需要将一些DLL注入某些进程中才能执行此操作,但是在获取不涉及DLL注入或将不同的DLL注入不同进程中的信息时,可能还有其他方法。在那种情况下,测试在确实的位置注入了哪些DLL会破坏“无论如何都会获得信息”规范提供的封装。 Dirk Herrmann的答案对此案进行了讨论。