Skip to main content
Version: 1.1.1

kMock 常见问题解答

当我在模拟对象上调用方法时,会调用真实对象的方法。有什么问题吗?

为了让一个方法被模拟,它必须是“虚拟的”,除非你使用 高性能依赖注入技术

我可以模拟可变参数函数吗?

您不能模拟可变参数函数(即采用省略号(...)的函数 参数)直接在 kMock 中。

问题是,一般来说,模拟对象“没有办法”知道如何 许多参数被传递给可变参数方法,以及参数的类型 是。只有基类的作者知道协议,我们无法查看 进入他或她的头脑。

因此,要模拟这样的函数,用户必须教导模拟对象如何 找出参数的数量及其类型。一种方法是 提供该函数的重载版本。

省略号参数继承自 C,并不是真正的 C++ 功能。他们是 使用不安全,并且不适用于具有构造函数或 析构函数。因此我们建议在 C++ 中尽可能避免使用它们。

当我定义带有 const 参数的模拟方法时,MSVC 会发出警告 C4301 或 C4373。为什么?

如果您使用 Microsoft Visual C++ 2005 SP1 编译它:

class Foo {
...
virtual void Bar(const int i) = 0;
};

class MockFoo : public Foo {
...
MOCK_METHOD(void, Bar, (const int i), (override));
};

您可能会收到以下警告:

warning C4301: 'MockFoo::Bar': overriding virtual function only differs from 'Foo::Bar' by const/volatile qualifier

这是一个 MSVC 错误。例如,相同的代码可以使用 gcc 编译良好。如果你 使用 Visual C++ 2008 SP1,您会收到警告:

warning C4373: 'MockFoo::Bar': virtual function overrides 'Foo::Bar', previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers

在 C++ 中,如果您使用“const”参数“声明”一个函数,则“const” 修饰符被忽略。因此,上面的Foo基类相当于:

class Foo {
...
virtual void Bar(int i) = 0; // int or const int? Makes no difference.
};

事实上,您可以使用“int”参数声明“Bar()”,并使用 const int 参数。编译器仍然会匹配它们。

由于在方法声明中使用参数“const”是没有意义的,因此我们 建议在 FooMockFoo 中删除它。这应该可以解决 VC 错误。

请注意,我们在这里讨论的是顶级 const 修饰符。如果 函数参数通过指针或引用传递,声明被指针或 裁判作为“const”仍然有意义。例如下面两个 声明等效:

void Bar(int* p);         // Neither p nor *p is const.
void Bar(const int* p); // p is not const, but *p is.

我不明白为什么 kMock 认为我的期望没有得到满足。我应该怎么办?

您可能想使用--kmock_verbose=info运行测试。这个标志让 kMock 打印它收到的每个模拟函数调用的跟踪。通过研究 跟踪,您将深入了解为什么您设定的期望未得到满足。

如果您看到消息“模拟函数没有默认操作集,并且其 返回类型没有设置默认值。”,然后尝试 添加默认操作。由于一个已知问题, 对没有默认操作的模拟的意外调用不会打印出详细信息 实际参数与预期参数之间的比较。

###我的程序崩溃了,ScopedMockLog输出大量消息。这是 kMock 的错误吗?

kMock 和 ScopedMockLog 可能在这里做正确的事情。

当测试崩溃时,失败信号处理程序将尝试记录大量 信息(例如堆栈跟踪和地址映射)。消息 如果您有许多具有深度堆栈的线程,则情况会变得复杂。当ScopedMockLog时 拦截这些消息并发现它们与任何期望不符,它 为每个人打印一个错误。

你可以学会忽略错误,或者你可以重写你的期望 您的测试更加稳健,例如,通过添加以下内容:

using ::testing::AnyNumber;
using ::testing::Not;
...
// Ignores any log not done by us.
EXPECT_CALL(log, Log(_, Not(EndsWith("/my_file.cc")), _))
.Times(AnyNumber());

我如何断言函数永远不会被调用?

using ::testing::_;
...
EXPECT_CALL(foo, Bar(_))
.Times(0);

我有一个失败的测试,kMock 两次告诉我某个特定的期望没有得到满足。这不是多余的吗?

当kMock检测到失败时,它会打印相关信息(mock函数 参数、相关期望的状态等)以帮助用户调试。 如果检测到另一个故障,kMock 将执行相同的操作,包括打印 相关期望的状态。

有时期望的状态在两次失败之间没有改变,你会 看到相同的状态描述两次。然而它们不是多余的, 因为它们指的是不同的时间点。事实上它们是相同的 有趣的信息。

使用模拟对象时,我遇到堆检查失败,但使用真实对象就可以了。有什么问题吗?

您正在模拟的类(希望是纯接口)是否有一个虚拟的 析构函数?

每当从基类派生时,请确保其析构函数是虚拟的。 否则就会发生不好的事情。考虑以下代码:

class Base {
public:
// Not virtual, but should be.
~Base() { ... }
...
};

class Derived : public Base {
public:
...
private:
std::string value_;
};

...
Base* p = new Derived;
...
delete p; // Surprise! ~Base() will be called, but ~Derived() will not
// - value_ is leaked.

通过将 ~Base() 更改为 virtual,~Derived() 将在以下情况下正确调用: delete p 被执行,堆检查器会很高兴。

“新的期望优先于旧的”规则使得书写期望变得尴尬。 kMock 为什么要这么做?

当人们抱怨这一点时,他们通常指的是这样的代码:

using ::testing::Return;
...
// foo.Bar() should be called twice, return 1 the first time, and return
// 2 the second time. However, I have to write the expectations in the
// reverse order. This sucks big time!!!
EXPECT_CALL(foo, Bar())
.WillOnce(Return(2))
.RetiresOnSaturation();
EXPECT_CALL(foo, Bar())
.WillOnce(Return(1))
.RetiresOnSaturation();

问题是他们没有选择最佳的方式来表达测试的 意图。

默认情况下,期望不必以任何特定顺序匹配。如果 如果你希望它们按照一定的顺序匹配,你需要明确。这是 kMock(和 jMock)的基本理念:很容易意外 过度指定您的测试,我们希望增加这样做的难度。

有两种更好的方法来编写测试规范。你可以把 期望顺序:

using ::testing::Return;
...
// foo.Bar() should be called twice, return 1 the first time, and return
// 2 the second time. Using a sequence, we can write the expectations
// in their natural order.
{
InSequence s;
EXPECT_CALL(foo, Bar())
.WillOnce(Return(1))
.RetiresOnSaturation();
EXPECT_CALL(foo, Bar())
.WillOnce(Return(2))
.RetiresOnSaturation();
}

或者你可以将动作序列放在相同的期望中:

using ::testing::Return;
...
// foo.Bar() should be called twice, return 1 the first time, and return
// 2 the second time.
EXPECT_CALL(foo, Bar())
.WillOnce(Return(1))
.WillOnce(Return(2))
.RetiresOnSaturation();

回到最初的问题:为什么 kMock 搜索期望(以及 ON_CALLs) 从后到前?因为这允许用户设置模拟 早期常见情况的行为(例如,在模拟的构造函数或测试中 夹具的设置阶段)并稍后使用更具体的规则对其进行自定义。如果 kMock 从前往后搜索,这种非常有用的模式是不可能的。

当调用没有 EXPECT_CALL 的函数时,kMock 会打印警告,即使我已使用 ON_CALL 设置其行为。在这种情况下不显示警告是否合理?

当在整洁和安全之间做出选择时,我们倾向于后者。所以 答案是我们认为最好显示警告。

人们经常在模拟对象的构造函数或 SetUp() 中编写 ON_CALL ,如下所示 默认行为很少因测试而改变。然后在测试体中 他们设定了期望,每次测试的期望通常都不同。拥有一个 测试设置部分中的“ON_CALL”并不意味着调用是预期的。 如果没有“EXPECT_CALL”并且调用了该方法,则可能是一个错误。如果 我们悄悄地让呼叫进行而不通知用户,错误可能会悄悄出现 未被注意到。

但是,如果您确定调用正常,则可以编写

using ::testing::_;
...
EXPECT_CALL(foo, Bar(_))
.WillRepeatedly(...);

而不是

using ::testing::_;
...
ON_CALL(foo, Bar(_))
.WillByDefault(...);

这告诉 kMock 您确实期望这些调用,并且不应打印任何警告。

此外,您可以通过指定“--kmock_verbose=error”来控制详细程度。其他 值为“信息”和“警告”。如果您发现输出噪音太大 调试时,只需选择不太详细的级别即可。

如何删除动作中模拟函数的参数?

如果您的模拟函数采用指针参数并且您想删除它 参数,您可以使用 testing::DeleteArg<N>() 删除第 N 个(零索引) 争论:

using ::testing::_;
...
MOCK_METHOD(void, Bar, (X* x, const Y& y));
...
EXPECT_CALL(mock_foo_, Bar(_, _))
.WillOnce(testing::DeleteArg<0>()));

如何对模拟函数的参数执行任意操作?

如果您发现自己需要执行某些不支持的操作 直接使用 kMock,记住您可以使用定义自己的操作 MakeAction()MakePolymorphicAction(),或者您可以编写一个存根函数 并使用 Invoke() 调用它。

using ::testing::_;
using ::testing::Invoke;
...
MOCK_METHOD(void, Bar, (X* p));
...
EXPECT_CALL(mock_foo_, Bar(_))
.WillOnce(Invoke(MyAction(...)));

我的代码调用静态/全局函数。我可以mock它吗?

可以,但你需要做出一些改变。

一般来说,如果您发现自己需要模拟静态函数,这是一个标志 你的模块耦合太紧密(灵活性较差,可重用性较差, 可测试性较差等)。您最好定义一个小接口,然后 通过该接口调用该函数,然后可以轻松地模拟该函数。它是 最初需要做一些工作,但通常很快就能收回成本。

这个谷歌测试博客 帖子 说 非常好。一探究竟。

我的模拟对象需要做复杂的事情。指定操作是很痛苦的。 kMock 太糟糕了!

我知道这不是一个问题,但无论如何你都可以免费得到答案。 :-)

使用 kMock,您可以轻松地用 C++ 创建模拟。人们可能会忍不住 到处使用它们。有时它们工作得很好,有时你可能会发现它们, 好吧,使用起来很痛苦。那么,后一种情况出了什么问题呢?

当您在不使用模拟的情况下编写测试时,您可以执行代码并断言 它返回正确的值或系统处于预期状态。这是 有时称为“基于状态的测试”。

模拟非常适合某些人所说的“基于交互”的测试:而不是 在最后检查系统状态,模拟对象验证它们是否是 以正确的方式调用,出现错误第一时间报告,给您一个 处理触发错误的精确上下文。这常常是 比基于州的测试更有效、更经济。

如果您正在进行基于状态的测试并使用测试替身来模拟 真实的物体,你最好使用假的。在此使用模拟 case 会引起痛苦,因为模拟执行复杂操作并不是强项 行动。如果你经历过这种情况并认为模拟很糟糕,那么你就不是 使用正确的工具来解决您的问题。或者,您可能正在尝试解决 错误的问题。 :-)

我收到警告“遇到无趣的函数调用 - 采取了默认操作..”我应该惊慌吗?

无论如何,不​​!这只是仅供参考。 :-)

这意味着你有一个模拟函数,你还没有设定任何期望 就它而言(根据 kMock 的规则,这意味着您对对此的调用不感兴趣) 函数,因此它可以被调用任意多次),并且它被调用。 没关系 - 你没有说调用该函数是不行的!

如果您实际上打算禁止调用此函数,但忘记了怎么办 写“EXPECT_CALL(foo, Bar()).Times(0)”?虽然有人可以说这是 如果是用户的错,kMock 会尽力友善地给你打印一条注释。

因此,当您看到该消息并认为不应该有任何 无趣的电话,你应该调查一下发生了什么。为了让你的生活 更简单的是,当遇到不感兴趣的调用时,kMock 会转储堆栈跟踪。 由此您可以找出它是哪个模拟函数以及它是如何被调用的。

我想定义一个自定义操作。我应该使用 Invoke() 还是实现 ActionInterface 接口?

无论哪种方式都可以 - 您想选择对您来说更方便的一种 环境。

通常,如果您的操作针对特定函数类型,请使用以下命令定义它 Invoke() 应该更容易;如果你的动作可以用在以下函数中 不同的类型(例如,如果您定义“Return(value)”), MakePolymorphicAction()是最简单的。有时您想要精确控制什么 可以使用该操作的函数类型,以及实现“ActionInterface” 是去这里的路。请参阅“kmock-actions.h”中“Return()”的实现 举个例子。

我在 WillOnce() 中使用 SetArgPointee(),但 gcc 抱怨“指定的返回类型冲突”。这是什么意思?

您收到此错误是因为 kMock 不知道当 调用模拟方法。 SetArgPointee() 说明了副作用是什么,但是 没有说返回值应该是什么。你需要 DoAll() 来链接一个 SetArgPointee()Return() 提供适合 API 的值 被嘲笑。

有关更多详细信息,请参阅此食谱 一个例子。

我有一个巨大的模拟类,Microsoft Visual C++ 在编译它时耗尽了内存。我能做些什么?

我们注意到,当使用/clr编译器标志时,Visual C++ 使用 5~6 编译模拟类时内存的两倍。我们建议避免/clr` 编译本机 C++ 模拟时。