在设置阶段忽略模拟调用

问题描述 投票:2回答:3

我经常面临的问题是,在测试的“有趣”部分开始之前,需要将模拟对象带入某种状态。

例如,假设我想测试以下类:

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无趣的召唤给出了一个恼人的警告。这个问题似乎有两个常见的解决方法:

  1. 使用NiceMock<Mock>来抑制所有这些警告;和
  2. 添加EXPECT_CALL(mock, onEnable())声明。

我不想使用第一种方法,因为可能有其他无趣的调用确实不应该发生。我也不喜欢第二种方法,因为我已经测试过(在第一次测试中)在启用系统时调用onEnable;因此,我不想在所有适用于已启用系统的测试中重复这种期望。

我希望能够做到的是,应该完全忽略所有模拟调用到某一点。在这个例子中,我希望只从“测试的有趣部分”评论开始检查期望。

有没有办法使用Google Mock实现这一目标?

c++ unit-testing googletest googlemock
3个回答
3
投票

令人讨厌的是,必要的函数在那里: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访问私有函数。


0
投票

根据设计,gmock无法实现您所寻求的灵活性。来自gmock Cookbook(强调我的):

[...]你应该非常谨慎地使用唠叨或严格的嘲讽,因为它们往往使测试更脆弱,更难维护。当您重构代码而不改变其外部可见行为时,理想情况下您不需要更新任何测试。但是,如果您的代码与愚蠢的模拟进行交互,那么您可能会因为更改而开始收到警告。更糟糕的是,如果您的代码与严格模拟交互,您的测试可能会开始失败,您将被迫修复它们。我们的一般建议是在大多数情况下使用漂亮的模拟(默认情况下),在开发或调试测试时使用naggy mocks(当前默认模式),并且仅使用严格的模拟作为最后的手段。

不幸的是,这是我们和许多其他开发人员遇到的问题。 Jeff Langr在他的书“Modern C++ Programming with Test-Driven Development”中写道(第5章,关于测试双打):

测试设计怎么样?当我们从手动模拟解决方案改为使用Google Mock时,我们将一个测试分成两个。如果我们在单个测试中表达了所有内容,那么一个测试可以设定涵盖所有三个重要事件的期望。这是一个简单的解决方案,但我们最终会得到一个混乱的测试。

[...]

通过使用NiceMock,我们承担了一个小风险。如果代码稍后以某种方式更改为在[...]接口上调用另一个方法,我们的测试就不会知道它了。您应该在需要时使用NiceMock,而不是习惯性的。如果您经常需要它,请寻求修复您的设计。


-1
投票

你可能最好使用不同的模拟类进行第二次测试。

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;
}
© www.soinside.com 2019 - 2024. All rights reserved.