我经常面临的问题是,在测试的“有趣”部分开始之前,需要将模拟对象带入某种状态。
例如,假设我想测试以下类:
struct ToTest
{
virtual void onEnable();
virtual void doAction();
};
因此,我创建了以下mock类:
struct Mock : ToTest
{
MOCK_METHOD0(onEnable, void());
MOCK_METHOD0(doAction, void());
};
第一个测试是在启用使用onEnable
对象的系统时调用ToTest
:
TEST(SomeTest, OnEnable)
{
Mock mock;
// register mock somehow
// interesting part of the test
EXPECT_CALL(mock, onEnable());
EnableSystem();
}
到现在为止还挺好。第二个测试是在系统执行操作并启用时调用doAction
。因此,应该在测试的有趣部分开始之前启用系统:
TEST(SomeTest, DoActionWhenEnabled)
{
Mock mock;
// register mock somehow
// initialize system
EnableSystem();
// interesting part of the test
EXPECT_CALL(mock, doAction());
DoSomeAction();
}
这有效,但是对于对onEnable
无趣的召唤给出了一个恼人的警告。这个问题似乎有两个常见的解决方法:
NiceMock<Mock>
来抑制所有这些警告;和EXPECT_CALL(mock, onEnable())
声明。我不想使用第一种方法,因为可能有其他无趣的调用确实不应该发生。我也不喜欢第二种方法,因为我已经测试过(在第一次测试中)在启用系统时调用onEnable
;因此,我不想在所有适用于已启用系统的测试中重复这种期望。
我希望能够做到的是,应该完全忽略所有模拟调用到某一点。在这个例子中,我希望只从“测试的有趣部分”评论开始检查期望。
有没有办法使用Google Mock实现这一目标?
令人讨厌的是,必要的函数在那里:gmock/gmock-spec-builders.h
定义Mock::AllowUninterestingCalls
和其他来控制特定模拟对象的警告的生成。使用这些功能,应该可以暂时禁用有关不感兴趣的呼叫的警告。
然而,问题是这些功能是私有的。好消息是,班级Mock
有一些可以被滥用的模板朋友(例如,NiceMock
)。所以我创建了以下解决方法:
namespace testing
{
// HACK: NiceMock<> is a friend of Mock so we specialize it here to a type that
// is never used to be able to temporarily make a mock nice. If this feature
// would just be supported, we wouldn't need this hack...
template<>
struct NiceMock<void>
{
static void allow(const void* mock)
{
Mock::AllowUninterestingCalls(mock);
}
static void warn(const void* mock)
{
Mock::WarnUninterestingCalls(mock);
}
static void fail(const void* mock)
{
Mock::FailUninterestingCalls(mock);
}
};
typedef NiceMock<void> UninterestingCalls;
}
这让我可以通过UninterestingCalls
typedef访问私有函数。
根据设计,gmock无法实现您所寻求的灵活性。来自gmock Cookbook(强调我的):
[...]你应该非常谨慎地使用唠叨或严格的嘲讽,因为它们往往使测试更脆弱,更难维护。当您重构代码而不改变其外部可见行为时,理想情况下您不需要更新任何测试。但是,如果您的代码与愚蠢的模拟进行交互,那么您可能会因为更改而开始收到警告。更糟糕的是,如果您的代码与严格模拟交互,您的测试可能会开始失败,您将被迫修复它们。我们的一般建议是在大多数情况下使用漂亮的模拟(默认情况下),在开发或调试测试时使用naggy mocks(当前默认模式),并且仅使用严格的模拟作为最后的手段。
不幸的是,这是我们和许多其他开发人员遇到的问题。 Jeff Langr在他的书“Modern C++ Programming with Test-Driven Development”中写道(第5章,关于测试双打):
测试设计怎么样?当我们从手动模拟解决方案改为使用Google Mock时,我们将一个测试分成两个。如果我们在单个测试中表达了所有内容,那么一个测试可以设定涵盖所有三个重要事件的期望。这是一个简单的解决方案,但我们最终会得到一个混乱的测试。
[...]
通过使用
NiceMock
,我们承担了一个小风险。如果代码稍后以某种方式更改为在[...]接口上调用另一个方法,我们的测试就不会知道它了。您应该在需要时使用NiceMock
,而不是习惯性的。如果您经常需要它,请寻求修复您的设计。
你可能最好使用不同的模拟类进行第二次测试。
class MockOnAction : public ToTest {
// This is a non-mocked function that does nothing
virtual void onEnable() {}
// Mocked function
MOCK_METHOD0(doAction, void());
}
为了使这个测试起作用,你可以让onEnable什么都不做(如上所示)。或者它可以做一些特殊的事情,比如调用基类或做其他逻辑。
virtual void onEnable() {
// You could call the base class version of this function
ToTest::onEnable();
// or hardcode some other logic
// isEnabled = true;
}