gMock 食谱
您可以在这里找到使用 gMock 的配方。如果您还没有阅读过新手指南,请先阅读,以确保您理解基础知识。
注意: gMock 位于 testing
命名空间中。为了提高可读性,建议在使用 gMock 定义的名称 Foo
之前,在文件中编写一次 using ::testing::Foo;
。为了简洁起见,我们省略了本节中的此类 using
语句,但您应该在自己的代码中执行此操作。
创建 Mock 类
Mock 类被定义为普通类,使用 MOCK_METHOD
宏来生成模拟方法。 该宏有 3 或 4 个参数
class MyMock {
public:
MOCK_METHOD(ReturnType, MethodName, (Args...));
MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...));
};
前 3 个参数只是方法声明,分为 3 部分。第 4 个参数接受一个封闭的限定符列表,这些限定符会影响生成的方法
const
- 使模拟方法成为const
方法。如果覆盖const
方法是必需的。override
- 使用override
标记该方法。如果覆盖virtual
方法,建议使用此限定符。noexcept
- 使用noexcept
标记该方法。如果覆盖noexcept
方法,则为必需。Calltype(...)
- 设置该方法的调用类型(例如STDMETHODCALLTYPE
),在 Windows 中很有用。ref(...)
- 使用指定的引用限定标记该方法。如果覆盖具有引用限定的方法,则为必需。例如ref(&)
或ref(&&)
。
处理未受保护的逗号
未受保护的逗号,即未被括号包围的逗号,会阻止 MOCK_METHOD
正确解析其参数
class MockFoo {
public:
MOCK_METHOD(std::pair<bool, int>, GetPair, ()); // Won't compile!
MOCK_METHOD(bool, CheckMap, (std::map<int, double>, bool)); // Won't compile!
};
解决方案 1 - 用括号包裹
class MockFoo {
public:
MOCK_METHOD((std::pair<bool, int>), GetPair, ());
MOCK_METHOD(bool, CheckMap, ((std::map<int, double>), bool));
};
请注意,通常用括号包裹返回类型或参数类型是无效的 C++。MOCK_METHOD
会删除括号。
解决方案 2 - 定义别名
class MockFoo {
public:
using BoolAndInt = std::pair<bool, int>;
MOCK_METHOD(BoolAndInt, GetPair, ());
using MapIntDouble = std::map<int, double>;
MOCK_METHOD(bool, CheckMap, (MapIntDouble, bool));
};
模拟私有或受保护的方法
您必须始终将模拟方法定义 (MOCK_METHOD
) 放在 mock 类的 public:
部分中,无论要模拟的方法在基类中是 public
、protected
还是 private
。这允许 ON_CALL
和 EXPECT_CALL
从模拟类外部引用模拟函数。(是的,C++ 允许子类更改基类中虚函数的访问级别。)例如
class Foo {
public:
...
virtual bool Transform(Gadget* g) = 0;
protected:
virtual void Resume();
private:
virtual int GetTimeOut();
};
class MockFoo : public Foo {
public:
...
MOCK_METHOD(bool, Transform, (Gadget* g), (override));
// The following must be in the public section, even though the
// methods are protected or private in the base class.
MOCK_METHOD(void, Resume, (), (override));
MOCK_METHOD(int, GetTimeOut, (), (override));
};
模拟重载方法
您可以像往常一样模拟重载函数。 不需要特别注意
class Foo {
...
// Must be virtual as we'll inherit from Foo.
virtual ~Foo();
// Overloaded on the types and/or numbers of arguments.
virtual int Add(Element x);
virtual int Add(int times, Element x);
// Overloaded on the const-ness of this object.
virtual Bar& GetBar();
virtual const Bar& GetBar() const;
};
class MockFoo : public Foo {
...
MOCK_METHOD(int, Add, (Element x), (override));
MOCK_METHOD(int, Add, (int times, Element x), (override));
MOCK_METHOD(Bar&, GetBar, (), (override));
MOCK_METHOD(const Bar&, GetBar, (), (const, override));
};
注意: 如果您不模拟重载方法的所有版本,编译器会警告您基类中的某些方法被隐藏。 要解决此问题,请使用 using
将它们引入作用域
class MockFoo : public Foo {
...
using Foo::Add;
MOCK_METHOD(int, Add, (Element x), (override));
// We don't want to mock int Add(int times, Element x);
...
};
模拟类模板
您可以像模拟任何类一样模拟类模板。
template <typename Elem>
class StackInterface {
...
// Must be virtual as we'll inherit from StackInterface.
virtual ~StackInterface();
virtual int GetSize() const = 0;
virtual void Push(const Elem& x) = 0;
};
template <typename Elem>
class MockStack : public StackInterface<Elem> {
...
MOCK_METHOD(int, GetSize, (), (const, override));
MOCK_METHOD(void, Push, (const Elem& x), (override));
};
模拟非虚方法
gMock 可以模拟非虚函数,以便在高性能依赖注入中使用。
在这种情况下,您的 mock 类不会与真实类共享一个公共基类,而是与真实类无关,但包含具有相同签名的方法。 模拟非虚函数的语法与模拟虚函数的语法相同(只是不要添加 override
)
// A simple packet stream class. None of its members is virtual.
class ConcretePacketStream {
public:
void AppendPacket(Packet* new_packet);
const Packet* GetPacket(size_t packet_number) const;
size_t NumberOfPackets() const;
...
};
// A mock packet stream class. It inherits from no other, but defines
// GetPacket() and NumberOfPackets().
class MockPacketStream {
public:
MOCK_METHOD(const Packet*, GetPacket, (size_t packet_number), (const));
MOCK_METHOD(size_t, NumberOfPackets, (), (const));
...
};
请注意,与真实类不同,mock 类未定义 AppendPacket()
。 只要测试不需要调用它,这就可以了。
接下来,您需要一种方式来说明您想在生产代码中使用 ConcretePacketStream
,并在测试中使用 MockPacketStream
。 由于这些函数不是虚函数,并且这两个类无关,因此您必须在编译时指定您的选择(而不是运行时)。
一种方法是对需要使用数据包流的代码进行模板化。 更具体地说,您将为您的代码提供一个模板类型参数,用于数据包流的类型。 在生产中,您将使用 ConcretePacketStream
作为类型参数来实例化您的模板。 在测试中,您将使用 MockPacketStream
实例化相同的模板。 例如,您可以编写
template <class PacketStream>
void CreateConnection(PacketStream* stream) { ... }
template <class PacketStream>
class PacketReader {
public:
void ReadPackets(PacketStream* stream, size_t packet_num);
};
然后,您可以在生产代码中使用 CreateConnection<ConcretePacketStream>()
和 PacketReader<ConcretePacketStream>
,并在测试中使用 CreateConnection<MockPacketStream>()
和 PacketReader<MockPacketStream>
。
MockPacketStream mock_stream;
EXPECT_CALL(mock_stream, ...)...;
.. set more expectations on mock_stream ...
PacketReader<MockPacketStream> reader(&mock_stream);
... exercise reader ...
模拟自由函数
无法直接模拟自由函数(即 C 风格的函数或静态方法)。 如果需要,您可以重写代码以使用接口(抽象类)。
与其直接调用自由函数(例如,OpenFile
),不如为其引入一个接口,并让一个具体的子类调用该自由函数
class FileInterface {
public:
...
virtual bool Open(const char* path, const char* mode) = 0;
};
class File : public FileInterface {
public:
...
bool Open(const char* path, const char* mode) override {
return OpenFile(path, mode);
}
};
您的代码应该与 FileInterface
通信以打开文件。 现在很容易模拟该函数。
这看起来很麻烦,但实际上,您通常会有多个相关函数可以放在同一个接口中,因此每个函数的语法开销会低得多。
如果您担心虚函数产生的性能开销,并且分析证实了您的担忧,您可以将此与 模拟非虚方法 的配方结合起来。
或者,您可以重写代码以接受 std::function 而不是自由函数,而不是引入新接口,然后使用 MockFunction 来模拟 std::function。
旧式 MOCK_METHODn
宏
在 2018 年引入通用 MOCK_METHOD
宏之前,mocks 是使用统称为 MOCK_METHODn
的宏系列创建的。 这些宏仍然受支持,但建议迁移到新的 MOCK_METHOD
。
MOCK_METHODn
系列中的宏与 MOCK_METHOD
不同
- 一般结构是
MOCK_METHODn(MethodName, ReturnType(Args))
,而不是MOCK_METHOD(ReturnType, MethodName, (Args))
。 - 数字
n
必须等于参数的数量。 - 模拟 const 方法时,必须使用
MOCK_CONST_METHODn
。 - 模拟类模板时,宏名称必须以
_T
结尾。 - 为了指定调用类型,宏名称必须以
_WITH_CALLTYPE
结尾,并且调用类型是第一个宏参数。
旧宏及其新的等效项
简单 | |
---|---|
旧 | MOCK_METHOD1(Foo, bool(int)) |
新 | MOCK_METHOD(bool, Foo, (int)) |
Const 方法 | |
旧 | MOCK_CONST_METHOD1(Foo, bool(int)) |
新 | MOCK_METHOD(bool, Foo, (int), (const)) |
类模板中的方法 | |
旧 | MOCK_METHOD1_T(Foo, bool(int)) |
新 | MOCK_METHOD(bool, Foo, (int)) |
类模板中的 Const 方法 | |
旧 | MOCK_CONST_METHOD1_T(Foo, bool(int)) |
新 | MOCK_METHOD(bool, Foo, (int), (const)) |
具有调用类型的方法 | |
旧 | MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int)) |
新 | MOCK_METHOD(bool, Foo, (int), (Calltype(STDMETHODCALLTYPE))) |
具有调用类型的 Const 方法 | |
旧 | MOCK_CONST_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int)) |
新 | MOCK_METHOD(bool, Foo, (int), (const, Calltype(STDMETHODCALLTYPE))) |
类模板中具有调用类型的方法 | |
旧 | MOCK_METHOD1_T_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int)) |
新 | MOCK_METHOD(bool, Foo, (int), (Calltype(STDMETHODCALLTYPE))) |
类模板中具有调用类型的 Const 方法 | |
旧 | MOCK_CONST_METHOD1_T_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int)) |
新 | MOCK_METHOD(bool, Foo, (int), (const, Calltype(STDMETHODCALLTYPE))) |
Nice、Strict 和 Naggy
如果一个 mock 方法没有 EXPECT_CALL
规范,但被调用了,我们称之为“无趣调用”,并且该方法的默认行为(可以使用 ON_CALL()
指定)将被执行。目前,默认情况下,一个无趣调用也会导致 gMock 打印一个警告。
然而,有时你可能想要忽略这些无趣调用,有时你可能想要将它们视为错误。gMock 允许你基于每个 mock 对象做出决定。
假设你的测试使用一个 mock 类 MockFoo
TEST(...) {
MockFoo mock_foo;
EXPECT_CALL(mock_foo, DoThis());
... code that uses mock_foo ...
}
如果 mock_foo
的一个方法(除了 DoThis()
之外)被调用,你将会收到一个警告。然而,如果你重写你的测试来使用 NiceMock<MockFoo>
代替,你可以抑制该警告
using ::testing::NiceMock;
TEST(...) {
NiceMock<MockFoo> mock_foo;
EXPECT_CALL(mock_foo, DoThis());
... code that uses mock_foo ...
}
NiceMock<MockFoo>
是 MockFoo
的一个子类,因此它可以被用在任何 MockFoo
可以接受的地方。
即使 MockFoo
的构造函数接受一些参数,它也可以工作,因为 NiceMock<MockFoo>
“继承”了 MockFoo
的构造函数
using ::testing::NiceMock;
TEST(...) {
NiceMock<MockFoo> mock_foo(5, "hi"); // Calls MockFoo(5, "hi").
EXPECT_CALL(mock_foo, DoThis());
... code that uses mock_foo ...
}
StrictMock
的用法类似,除了它使所有无趣调用都变成失败。
using ::testing::StrictMock;
TEST(...) {
StrictMock<MockFoo> mock_foo;
EXPECT_CALL(mock_foo, DoThis());
... code that uses mock_foo ...
// The test will fail if a method of mock_foo other than DoThis()
// is called.
}
注意:NiceMock
和 StrictMock
只影响无趣调用(没有 expectations 的方法的调用);它们不影响意外调用(有 expectations 的方法,但它们不匹配的调用)。参见 理解无趣调用 vs 意外调用。
不过,有一些需要注意的地方(遗憾的是,它们是 C++ 限制的副作用)
NiceMock<MockFoo>
和StrictMock<MockFoo>
仅适用于使用MOCK_METHOD
宏在MockFoo
类中直接定义的 mock 方法。如果一个 mock 方法是在MockFoo
的一个基类中定义的,“nice” 或 “strict” 修饰符可能不会影响它,这取决于编译器。特别地,嵌套NiceMock
和StrictMock
(例如NiceMock<StrictMock<MockFoo> >
) 是不被支持的。- 如果
MockFoo
的析构函数不是虚函数,NiceMock<MockFoo>
和StrictMock<MockFoo>
可能无法正常工作。我们希望修复这个问题,但这需要清理现有的测试。
最后,你应该对何时使用 naggy 或 strict mocks 非常谨慎,因为它们往往会使测试更加脆弱且难以维护。当你在不改变其外部可见行为的情况下重构你的代码时,理想情况下,你不需要更新任何测试。然而,如果你的代码与一个 naggy mock 交互,你可能会因为你的更改而开始收到大量的警告。更糟糕的是,如果你的代码与一个 strict mock 交互,你的测试可能会开始失败,你将被迫修复它们。我们通常建议在大多数时候使用 nice mocks(尚未成为默认值),在开发或调试测试时使用 naggy mocks(当前默认值),并且仅在万不得已的情况下才使用 strict mocks。
简化接口而不破坏现有代码
有时一个方法有一个很长的参数列表,其中大部分都是无趣的。例如
class LogSink {
public:
...
virtual void send(LogSeverity severity, const char* full_filename,
const char* base_filename, int line,
const struct tm* tm_time,
const char* message, size_t message_len) = 0;
};
这个方法的参数列表很长,很难使用(message
参数甚至不是以 0 结尾的)。如果我们按原样 mock 它,使用 mock 会很尴尬。然而,如果我们尝试简化这个接口,我们需要修复所有依赖它的客户端,这通常是不可行的。
诀窍是在 mock 类中重新分发该方法
class ScopedMockLog : public LogSink {
public:
...
void send(LogSeverity severity, const char* full_filename,
const char* base_filename, int line, const tm* tm_time,
const char* message, size_t message_len) override {
// We are only interested in the log severity, full file name, and
// log message.
Log(severity, full_filename, std::string(message, message_len));
}
// Implements the mock method:
//
// void Log(LogSeverity severity,
// const string& file_path,
// const string& message);
MOCK_METHOD(void, Log,
(LogSeverity severity, const string& file_path,
const string& message));
};
通过定义一个新的带有精简参数列表的 mock 方法,我们使 mock 类更加用户友好。
这种技术也可以应用于使重载方法更易于 mock。例如,当重载被用于实现默认参数时
class MockTurtleFactory : public TurtleFactory {
public:
Turtle* MakeTurtle(int length, int weight) override { ... }
Turtle* MakeTurtle(int length, int weight, int speed) override { ... }
// the above methods delegate to this one:
MOCK_METHOD(Turtle*, DoMakeTurtle, ());
};
这允许不关心哪个重载被调用的测试避免指定参数匹配器
ON_CALL(factory, DoMakeTurtle)
.WillByDefault(Return(MakeMockTurtle()));
Mock 具体类的替代方案
通常你可能会发现自己正在使用没有实现接口的类。为了测试你的使用此类(让我们称之为 Concrete
)的代码,你可能会很想使 Concrete
的方法是虚拟的,然后 mock 它。
尽量不要这样做。
将一个非虚函数变成虚函数是一个很大的决定。它创建了一个扩展点,子类可以在这里调整你的类的行为。这削弱了你对类的控制,因为现在更难维护类的不变性。只有当子类有充分的理由覆盖它时,你才应该使一个函数成为虚函数。
直接 mock 具体类是有问题的,因为它在类和测试之间创建了紧密的耦合 - 类中的任何小变化都可能使你的测试无效,并使测试维护成为一件痛苦的事情。
为了避免这些问题,许多程序员一直在实践“面向接口编程”:你的代码不会直接与 Concrete
类对话,而是定义一个接口并与之对话。然后,你在 Concrete
之上实现该接口作为一个适配器。在测试中,你可以轻松地 mock 该接口来观察你的代码是如何工作的。
这种技术会产生一些开销
- 你支付了虚函数调用的成本(通常不是问题)。
- 程序员需要学习更多的抽象。
然而,除了更好的可测试性之外,它还可以带来显著的好处
Concrete
的 API 可能不太适合你的问题域,因为你可能不是它试图服务的唯一客户端。通过设计你自己的接口,你就有机会根据你的需要来定制它 - 你可以添加更高级别的功能,重命名内容等等,而不仅仅是修剪类。这允许你以更自然的方式编写你的代码(接口的用户),这意味着它将更具可读性,更易于维护,并且你将更有效率。- 如果
Concrete
的实现必须更改,你不需要重写所有使用它的地方。相反,你可以将更改吸收到你的接口实现中,并且你的其他代码和测试将免受此更改的影响。
有些人担心如果每个人都在实践这种技术,他们最终会编写大量冗余代码。这种担忧是完全可以理解的。但是,有两个原因说明情况可能并非如此
- 不同的项目可能需要以不同的方式使用
Concrete
,因此它们的最佳接口将是不同的。因此,它们每个人都将在Concrete
之上拥有自己的特定于域的接口,并且它们的代码将不会相同。 - 如果有足够多的项目想要使用相同的接口,他们总是可以共享它,就像他们一直在共享
Concrete
一样。你可以将接口和适配器签入到Concrete
附近(可能在一个contrib
子目录中),并让许多项目使用它。
你需要仔细权衡你的特定问题的利弊,但我希望向你保证,Java 社区已经实践了很长时间,并且它是一种经过验证的有效技术,适用于各种情况。 :-)
将调用委托给 Fake
有时你有一个接口的非平凡的 fake 实现。例如
class Foo {
public:
virtual ~Foo() {}
virtual char DoThis(int n) = 0;
virtual void DoThat(const char* s, int* p) = 0;
};
class FakeFoo : public Foo {
public:
char DoThis(int n) override {
return (n > 0) ? '+' :
(n < 0) ? '-' : '0';
}
void DoThat(const char* s, int* p) override {
*p = strlen(s);
}
};
现在你想 mock 这个接口,以便你可以设置 expectations。但是,你还想使用 FakeFoo
作为默认行为,因为在 mock 对象中复制它需要做很多工作。
当你使用 gMock 定义 mock 类时,你可以让它将其默认行为委托给你已经拥有的 fake 类,使用这种模式
class MockFoo : public Foo {
public:
// Normal mock method definitions using gMock.
MOCK_METHOD(char, DoThis, (int n), (override));
MOCK_METHOD(void, DoThat, (const char* s, int* p), (override));
// Delegates the default actions of the methods to a FakeFoo object.
// This must be called *before* the custom ON_CALL() statements.
void DelegateToFake() {
ON_CALL(*this, DoThis).WillByDefault([this](int n) {
return fake_.DoThis(n);
});
ON_CALL(*this, DoThat).WillByDefault([this](const char* s, int* p) {
fake_.DoThat(s, p);
});
}
private:
FakeFoo fake_; // Keeps an instance of the fake in the mock.
};
这样,你可以像往常一样在测试中使用 MockFoo
。只要记住,如果你没有在 ON_CALL()
或 EXPECT_CALL()
中显式设置一个 action,fake 将被调用来执行它。
using ::testing::_;
TEST(AbcTest, Xyz) {
MockFoo foo;
foo.DelegateToFake(); // Enables the fake for delegation.
// Put your ON_CALL(foo, ...)s here, if any.
// No action specified, meaning to use the default action.
EXPECT_CALL(foo, DoThis(5));
EXPECT_CALL(foo, DoThat(_, _));
int n = 0;
EXPECT_EQ(foo.DoThis(5), '+'); // FakeFoo::DoThis() is invoked.
foo.DoThat("Hi", &n); // FakeFoo::DoThat() is invoked.
EXPECT_EQ(n, 2);
}
一些技巧
- 如果你愿意,你仍然可以通过提供你自己的
ON_CALL()
或者在EXPECT_CALL()
中使用.WillOnce()
/.WillRepeatedly()
来覆盖默认 action。 -
在
DelegateToFake()
中,你只需要委托那些你打算使用其 fake 实现的方法。 -
这里讨论的通用技术适用于重载方法,但您需要告诉编译器您指的是哪个版本。要消除 mock 函数(您在
ON_CALL()
的括号内指定的函数)的歧义,请使用此技术;要消除 fake 函数(您放在Invoke()
中的函数)的歧义,请使用static_cast
来指定函数的类型。例如,如果类Foo
有方法char DoThis(int n)
和bool DoThis(double x) const
,并且您想要调用后者,则需要编写Invoke(&fake_, static_cast<bool (FakeFoo::*)(double) const>(&FakeFoo::DoThis))
而不是Invoke(&fake_, &FakeFoo::DoThis)
(static_cast
的尖括号内的奇怪事物是指向第二个DoThis()
方法的函数指针的类型)。 - 必须混合使用 mock 和 fake 通常表明存在一些问题。 也许您还没有习惯基于交互的测试方法。 或者您的接口承担了太多的角色,应该将其拆分。 因此,不要滥用此功能。 我们只建议将其作为重构代码时的中间步骤。
关于混合使用 mock 和 fake 的提示,这里有一个例子说明为什么这可能是一个坏兆头:假设您有一个类 System
用于底层系统操作。 特别是,它执行文件和 I/O 操作。 假设您想测试您的代码如何使用 System
进行 I/O,并且您只希望文件操作正常工作。 如果您 mock 掉整个 System
类,则必须为文件操作部分提供 fake 实现,这表明 System
承担了太多的角色。
相反,您可以定义一个 FileOps
接口和一个 IOOps
接口,并将 System
的功能拆分为两个。 然后,您可以 mock IOOps
而无需 mock FileOps
。
将调用委派给真实对象
使用测试替身(mocks, fakes, stubs 等)时,它们的行为有时会与真实对象的行为不同。 这种差异可能是故意的(例如,模拟错误,以便您可以测试错误处理代码)或无意的。 如果您的 mocks 的行为与真实对象的行为有错误的不同,您最终可能会得到通过测试但在生产中失败的代码。
您可以使用委派给真实对象技术来确保您的 mock 具有与真实对象相同的行为,同时保留验证调用的能力。 此技术与 委派给 fake 技术非常相似,不同之处在于我们使用真实对象而不是 fake。 这是一个例子
using ::testing::AtLeast;
class MockFoo : public Foo {
public:
MockFoo() {
// By default, all calls are delegated to the real object.
ON_CALL(*this, DoThis).WillByDefault([this](int n) {
return real_.DoThis(n);
});
ON_CALL(*this, DoThat).WillByDefault([this](const char* s, int* p) {
real_.DoThat(s, p);
});
...
}
MOCK_METHOD(char, DoThis, ...);
MOCK_METHOD(void, DoThat, ...);
...
private:
Foo real_;
};
...
MockFoo mock;
EXPECT_CALL(mock, DoThis())
.Times(3);
EXPECT_CALL(mock, DoThat("Hi"))
.Times(AtLeast(1));
... use mock in test ...
这样,gMock 将验证您的代码是否进行了正确的调用(具有正确的参数,以正确的顺序,调用了正确的次数等),并且真实对象将响应调用(因此行为将与生产环境中的相同)。 这为您提供了两全其美的体验。
将调用委派给父类
理想情况下,您应该针对接口进行编码,接口的方法都是纯虚函数。 实际上,有时您确实需要 mock 一个不是纯虚的虚方法(即,它已经有一个实现)。 例如
class Foo {
public:
virtual ~Foo();
virtual void Pure(int n) = 0;
virtual int Concrete(const char* str) { ... }
};
class MockFoo : public Foo {
public:
// Mocking a pure method.
MOCK_METHOD(void, Pure, (int n), (override));
// Mocking a concrete method. Foo::Concrete() is shadowed.
MOCK_METHOD(int, Concrete, (const char* str), (override));
};
有时您可能想要调用 Foo::Concrete()
而不是 MockFoo::Concrete()
。 也许您想将其作为桩操作的一部分来执行,或者您的测试根本不需要 mock Concrete()
(但是每当您不需要 mock 它的方法之一时,都必须定义一个新的 mock 类,这将非常痛苦)。
您可以通过以下方式在 action 中调用 Foo::Concrete()
...
EXPECT_CALL(foo, Concrete).WillOnce([&foo](const char* str) {
return foo.Foo::Concrete(str);
});
或告诉 mock 对象您不想 mock Concrete()
...
ON_CALL(foo, Concrete).WillByDefault([&foo](const char* str) {
return foo.Foo::Concrete(str);
});
(为什么我们不直接写 { return foo.Concrete(str); }
呢? 如果你这样做,MockFoo::Concrete()
将被调用(并导致无限递归),因为 Foo::Concrete()
是虚函数。 这就是 C++ 的工作方式。)
使用匹配器
完全匹配参数值
您可以指定 mock 方法期望的确切参数
using ::testing::Return;
...
EXPECT_CALL(foo, DoThis(5))
.WillOnce(Return('a'));
EXPECT_CALL(foo, DoThat("Hello", bar));
使用简单匹配器
您可以使用匹配器来匹配具有特定属性的参数
using ::testing::NotNull;
using ::testing::Return;
...
EXPECT_CALL(foo, DoThis(Ge(5))) // The argument must be >= 5.
.WillOnce(Return('a'));
EXPECT_CALL(foo, DoThat("Hello", NotNull()));
// The second argument must not be NULL.
常用的匹配器是 _
,它匹配任何东西
EXPECT_CALL(foo, DoThat(_, NotNull()));
组合匹配器
您可以使用 AllOf()
、AllOfArray()
、AnyOf()
、AnyOfArray()
和 Not()
从现有匹配器构建复杂匹配器
using ::testing::AllOf;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::Ne;
using ::testing::Not;
...
// The argument must be > 5 and != 10.
EXPECT_CALL(foo, DoThis(AllOf(Gt(5),
Ne(10))));
// The first argument must not contain sub-string "blah".
EXPECT_CALL(foo, DoThat(Not(HasSubstr("blah")),
NULL));
匹配器是函数对象,参数化匹配器可以像任何其他函数一样组合。 但是,由于它们的类型可能很长并且很少提供有意义的信息,因此可以使用 C++14 泛型 lambda 来表达它们,以避免指定类型。 例如,
using ::testing::Contains;
using ::testing::Property;
inline constexpr auto HasFoo = [](const auto& f) {
return Property("foo", &MyClass::foo, Contains(f));
};
...
EXPECT_THAT(x, HasFoo("blah"));
转换匹配器
gMock 匹配器是静态类型的,这意味着如果您使用错误类型的匹配器(例如,如果您使用 Eq(5)
来匹配 string
参数),编译器可以捕获您的错误。 对你来说很好!
但是,有时您知道自己在做什么,并且希望编译器给您一些宽松。 一个例子是您有一个 long
的匹配器,而您想要匹配的参数是 int
。 虽然这两种类型并不完全相同,但使用 Matcher<long>
来匹配 int
并没有什么问题 - 毕竟,我们可以先将 int
参数无损地转换为 long
,然后再将其提供给匹配器。
为了支持这种需求,gMock 为您提供了 SafeMatcherCast<T>(m)
函数。 它将匹配器 m
转换为类型 Matcher<T>
。 为了确保安全,gMock 检查 (让 U
成为 m
接受的类型
- 类型
T
可以隐式转换为类型U
; - 当
T
和U
都是内置的算术类型(bool
、整数和浮点数)时,从T
到U
的转换不是有损的(换句话说,任何可以由T
表示的值也可以由U
表示);并且 - 当
U
是非 const 引用时,T
也必须是引用(因为底层匹配器可能对U
值的地址感兴趣)。
如果未满足这些条件中的任何一个,则代码将无法编译。
这是一个例子
using ::testing::SafeMatcherCast;
// A base class and a child class.
class Base { ... };
class Derived : public Base { ... };
class MockFoo : public Foo {
public:
MOCK_METHOD(void, DoThis, (Derived* derived), (override));
};
...
MockFoo foo;
// m is a Matcher<Base*> we got from somewhere.
EXPECT_CALL(foo, DoThis(SafeMatcherCast<Derived*>(m)));
如果您发现 SafeMatcherCast<T>(m)
太过局限,您可以使用类似的函数 MatcherCast<T>(m)
。 区别在于,只要您可以 static_cast
类型 T
到类型 U
,MatcherCast
就可以工作。
MatcherCast
本质上允许您绕过 C++ 的类型系统(static_cast
并不总是安全的,因为它可能会丢弃信息,例如),因此请注意不要滥用它。
选择重载函数
如果您希望调用重载函数,编译器可能需要一些帮助才能确定它是哪个重载版本。
要消除在这个对象的 const-ness 上重载的函数的歧义,请使用 Const()
参数包装器。
using ::testing::ReturnRef;
class MockFoo : public Foo {
...
MOCK_METHOD(Bar&, GetBar, (), (override));
MOCK_METHOD(const Bar&, GetBar, (), (const, override));
};
...
MockFoo foo;
Bar bar1, bar2;
EXPECT_CALL(foo, GetBar()) // The non-const GetBar().
.WillOnce(ReturnRef(bar1));
EXPECT_CALL(Const(foo), GetBar()) // The const GetBar().
.WillOnce(ReturnRef(bar2));
(Const()
由 gMock 定义,并返回对其参数的 const
引用。)
要消除具有相同数量的参数但参数类型不同的重载函数的歧义,您可能需要指定匹配器的确切类型,方法是将您的匹配器包装在 Matcher<type>()
中,或者使用类型固定的匹配器(TypedEq<type>
, An<type>()
等)
using ::testing::An;
using ::testing::Matcher;
using ::testing::TypedEq;
class MockPrinter : public Printer {
public:
MOCK_METHOD(void, Print, (int n), (override));
MOCK_METHOD(void, Print, (char c), (override));
};
TEST(PrinterTest, Print) {
MockPrinter printer;
EXPECT_CALL(printer, Print(An<int>())); // void Print(int);
EXPECT_CALL(printer, Print(Matcher<int>(Lt(5)))); // void Print(int);
EXPECT_CALL(printer, Print(TypedEq<char>('a'))); // void Print(char);
printer.Print(3);
printer.Print(6);
printer.Print('a');
}
根据参数执行不同的操作
当调用 mock 方法时,将选择最后匹配的并且仍然处于活动状态的期望(可以理解为“新的覆盖旧的”)。 因此,您可以根据其参数值使方法执行不同的操作,如下所示
using ::testing::_;
using ::testing::Lt;
using ::testing::Return;
...
// The default case.
EXPECT_CALL(foo, DoThis(_))
.WillRepeatedly(Return('b'));
// The more specific case.
EXPECT_CALL(foo, DoThis(Lt(5)))
.WillRepeatedly(Return('a'));
现在,如果使用小于 5 的值调用 foo.DoThis()
,则将返回 'a'
; 否则将返回 'b'
。
将多个参数作为一个整体进行匹配
有时,单独匹配参数是不够的。 例如,我们可能想说第一个参数必须小于第二个参数。 With()
子句允许我们将 mock 函数的所有参数作为一个整体进行匹配。 例如,
using ::testing::_;
using ::testing::Ne;
using ::testing::Lt;
...
EXPECT_CALL(foo, InRange(Ne(0), _))
.With(Lt());
表示 InRange()
的第一个参数不能为 0,并且必须小于第二个参数。
With()
中的表达式必须是 Matcher<std::tuple<A1, ..., An>>
类型的匹配器,其中 A1
, …, An
是函数参数的类型。
您也可以在 .With()
中编写 AllArgs(m)
而不是 m
。 这两种形式是等效的,但 .With(AllArgs(Lt()))
比 .With(Lt())
更具可读性。
您可以使用 Args<k1, ..., kn>(m)
来针对 m
匹配 n
个选定的参数(作为一个元组)。 例如,
using ::testing::_;
using ::testing::AllOf;
using ::testing::Args;
using ::testing::Lt;
...
EXPECT_CALL(foo, Blah)
.With(AllOf(Args<0, 1>(Lt()), Args<1, 2>(Lt())));
表示将使用参数 x
、y
和 z
调用 Blah
,其中 x < y < z
。 请注意,在此示例中,不必指定位置匹配器。
为方便起见和示例,gMock 提供了一些用于 2 元组的匹配器,包括上面的 Lt()
匹配器。 请参阅多参数匹配器,以获取完整列表。
请注意,如果您想将参数传递给您自己的谓词(例如 .With(Args<0, 1>(Truly(&MyPredicate)))
),则必须编写该谓词以接受 std::tuple
作为其参数; gMock 将传递 n
个选定的参数作为一个单独的元组传递给谓词。
将匹配器用作谓词
你有没有注意到,matcher 只是一个花哨的谓词,并且它也知道如何描述自己?许多现有的算法将谓词作为参数(例如,在 STL 的 <algorithm>
头文件中定义的那些),如果 gMock matcher 不允许参与,那将是一种遗憾。
幸运的是,你可以通过将 matcher 包装在 Matches()
函数中,在需要一元谓词 functor 的地方使用它。例如:
#include <algorithm>
#include <vector>
using ::testing::Matches;
using ::testing::Ge;
vector<int> v;
...
// How many elements in v are >= 10?
const int count = count_if(v.begin(), v.end(), Matches(Ge(10)));
由于你可以使用 gMock 轻松地从简单的 matcher 构建复杂的 matcher,因此这为你提供了一种方便的方式来构建复合谓词(使用 STL 的 <functional>
头文件做同样的事情非常痛苦)。例如,这是一个谓词,它对于任何 >= 0、<= 100 且 != 50 的数字都成立:
using ::testing::AllOf;
using ::testing::Ge;
using ::testing::Le;
using ::testing::Matches;
using ::testing::Ne;
...
Matches(AllOf(Ge(0), Le(100), Ne(50)))
在 googletest 断言中使用 Matcher
请参阅断言参考中的 EXPECT_THAT
。
将谓词用作 Matcher
gMock 提供了一组内置的 matcher,用于将参数与期望值进行匹配 - 更多信息请参阅 Matchers 参考。如果您发现内置的集合不足,你可以使用任意的一元谓词函数或 functor 作为 matcher - 只要该谓词接受您想要的类型的值。你可以通过将谓词包装在 Truly()
函数中来实现,例如:
using ::testing::Truly;
int IsEven(int n) { return (n % 2) == 0 ? 1 : 0; }
...
// Bar() must be called with an even number.
EXPECT_CALL(foo, Bar(Truly(IsEven)));
请注意,谓词函数/functor 不必返回 bool
。只要返回值可以用作语句 if (condition) ...
中的条件即可。
匹配不可复制的参数
当你执行 EXPECT_CALL(mock_obj, Foo(bar))
时,gMock 会保存 bar
的副本。当稍后调用 Foo()
时,gMock 会将 Foo()
的参数与 bar
的保存副本进行比较。这样,你无需担心在执行 EXPECT_CALL()
后 bar
被修改或销毁。当你使用 Eq(bar)
、Le(bar)
等 matcher 时,情况也是如此。
但是如果 bar
无法复制(即没有复制构造函数)怎么办?你可以定义自己的 matcher 函数或回调,并将其与 Truly()
一起使用,如前几个示例所示。或者,如果您可以保证在执行 EXPECT_CALL()
后 bar
不会被更改,则可能会避免这种情况。只需告诉 gMock 它应该保存对 bar
的引用,而不是它的副本。 方法如下:
using ::testing::Eq;
using ::testing::Lt;
...
// Expects that Foo()'s argument == bar.
EXPECT_CALL(mock_obj, Foo(Eq(std::ref(bar))));
// Expects that Foo()'s argument < bar.
EXPECT_CALL(mock_obj, Foo(Lt(std::ref(bar))));
记住:如果这样做,请不要在 EXPECT_CALL()
之后更改 bar
,否则结果将是未定义的。
验证对象的成员
通常,mock 函数将对对象的引用作为参数。在匹配参数时,你可能不希望将整个对象与固定对象进行比较,因为这可能过度指定。相反,你可能需要验证对象的某个成员变量或某个 getter 方法的结果。你可以使用 Field()
和 Property()
来做到这一点。更具体地说:
Field(&Foo::bar, m)
是一个 matcher,它匹配 Foo
对象,其 bar
成员变量满足 matcher m
。
Property(&Foo::baz, m)
是一个 matcher,它匹配 Foo
对象,其 baz()
方法返回一个满足 matcher m
的值。
例如:
表达式 | 描述 |
---|---|
Field(&Foo::number, Ge(3)) |
匹配 x ,其中 x.number >= 3 。 |
Property(&Foo::name, StartsWith("John ")) |
匹配 x ,其中 x.name() 以 "John " 开头。 |
请注意,在 Property(&Foo::baz, ...)
中,方法 baz()
必须不带参数,并且声明为 const
。不要对你不拥有的成员函数使用 Property()
,因为获取函数的地址是脆弱的,并且通常不是函数契约的一部分。
Field()
和 Property()
也可以匹配指向对象的普通指针。例如:
using ::testing::Field;
using ::testing::Ge;
...
Field(&Foo::number, Ge(3))
匹配普通指针 p
,其中 p->number >= 3
。如果 p
是 NULL
,则无论内部 matcher 如何,匹配都将始终失败。
如果要同时验证多个成员怎么办?请记住,有 AllOf()
和 AllOfArray()
。
最后,Field()
和 Property()
提供了重载,它们将字段或属性名称作为第一个参数,以便将其包含在错误消息中。这在创建组合 matcher 时很有用。
using ::testing::AllOf;
using ::testing::Field;
using ::testing::Matcher;
using ::testing::SafeMatcherCast;
Matcher<Foo> IsFoo(const Foo& foo) {
return AllOf(Field("some_field", &Foo::some_field, foo.some_field),
Field("other_field", &Foo::other_field, foo.other_field),
Field("last_field", &Foo::last_field, foo.last_field));
}
验证指针参数指向的值
C++ 函数通常将指针作为参数。你可以使用诸如 IsNull()
、NotNull()
和其他比较 matcher 来匹配指针,但是如果你想确保指针指向的值,而不是指针本身,具有某种属性怎么办?好吧,你可以使用 Pointee(m)
matcher。
仅当 m
匹配指针指向的值时,Pointee(m)
才匹配指针。例如:
using ::testing::Ge;
using ::testing::Pointee;
...
EXPECT_CALL(foo, Bar(Pointee(Ge(3))));
期望使用指向大于或等于 3 的值的指针来调用 foo.Bar()
。
关于 Pointee()
的一个优点是,它将 NULL
指针视为匹配失败,因此你可以编写 Pointee(m)
而不是
using ::testing::AllOf;
using ::testing::NotNull;
using ::testing::Pointee;
...
AllOf(NotNull(), Pointee(m))
而不必担心 NULL
指针会导致测试崩溃。
另外,我们是否告诉过你 Pointee()
适用于原始指针和智能指针(std::unique_ptr
、std::shared_ptr
等)?
如果你有一个指向指针的指针怎么办?你猜对了 - 你可以使用嵌套的 Pointee()
来更深入地探测值。例如,Pointee(Pointee(Lt(3)))
匹配一个指向一个指向一个小于 3 的数字的指针的指针(真是绕口令...)。
定义自定义 Matcher 类
大多数 matcher 可以使用 MATCHER* 宏简单地定义,这些宏简洁而灵活,并产生良好的错误消息。但是,这些宏对于它们创建的接口不是很明确,并且并非总是适合,尤其是对于将被广泛重用的 matcher。
对于更高级的情况,你可能需要定义自己的 matcher 类。自定义 matcher 允许你测试该对象的特定不变属性。让我们看看该怎么做。
假设你有一个 mock 函数,它接受一个 Foo
类型的对象,该对象具有一个 int bar()
方法和一个 int baz()
方法。你想要约束参数的 bar()
值加上其 baz()
值是一个给定的数字。(这是一个不变量。)这是我们如何编写和使用 matcher 类来做到这一点:
class BarPlusBazEqMatcher {
public:
using is_gtest_matcher = void;
explicit BarPlusBazEqMatcher(int expected_sum)
: expected_sum_(expected_sum) {}
bool MatchAndExplain(const Foo& foo,
std::ostream* /* listener */) const {
return (foo.bar() + foo.baz()) == expected_sum_;
}
void DescribeTo(std::ostream* os) const {
*os << "bar() + baz() equals " << expected_sum_;
}
void DescribeNegationTo(std::ostream* os) const {
*os << "bar() + baz() does not equal " << expected_sum_;
}
private:
const int expected_sum_;
};
::testing::Matcher<const Foo&> BarPlusBazEq(int expected_sum) {
return BarPlusBazEqMatcher(expected_sum);
}
...
Foo foo;
EXPECT_THAT(foo, BarPlusBazEq(5))...;
匹配容器
有时,STL 容器(例如 list、vector、map 等)被传递给 mock 函数,你可能想要验证它。由于大多数 STL 容器都支持 ==
运算符,因此你可以编写 Eq(expected_container)
或直接 expected_container
来完全匹配容器。
但是,有时你可能想要更灵活(例如,第一个元素必须完全匹配,但是第二个元素可以是任何正数,依此类推)。而且,测试中使用的容器通常具有少量元素,并且必须在线外定义期望的容器有点麻烦。
在这种情况下,你可以使用 ElementsAre()
或 UnorderedElementsAre()
matcher。
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Gt;
...
MOCK_METHOD(void, Foo, (const vector<int>& numbers), (override));
...
EXPECT_CALL(mock, Foo(ElementsAre(1, Gt(0), _, 5)));
上面的 matcher 表明容器必须有 4 个元素,它们必须分别为 1、大于 0、任何值和 5。
如果改为编写
using ::testing::_;
using ::testing::Gt;
using ::testing::UnorderedElementsAre;
...
MOCK_METHOD(void, Foo, (const vector<int>& numbers), (override));
...
EXPECT_CALL(mock, Foo(UnorderedElementsAre(1, Gt(0), _, 5)));
这意味着容器必须有 4 个元素,它们(在某种排列下)必须分别为 1、大于 0、任何值和 5。
作为替代方法,你可以将参数放在 C 风格的数组中,并使用 ElementsAreArray()
或 UnorderedElementsAreArray()
代替。
using ::testing::ElementsAreArray;
...
// ElementsAreArray accepts an array of element values.
const int expected_vector1[] = {1, 5, 2, 4, ...};
EXPECT_CALL(mock, Foo(ElementsAreArray(expected_vector1)));
// Or, an array of element matchers.
Matcher<int> expected_vector2[] = {1, Gt(2), _, 3, ...};
EXPECT_CALL(mock, Foo(ElementsAreArray(expected_vector2)));
如果需要动态创建数组(因此编译器无法推断出数组大小),则可以为 ElementsAreArray()
提供一个额外的参数来指定数组大小。
using ::testing::ElementsAreArray;
...
int* const expected_vector3 = new int[count];
... fill expected_vector3 with values ...
EXPECT_CALL(mock, Foo(ElementsAreArray(expected_vector3, count)));
在比较 map 或其他关联容器时使用 Pair
。
using ::testing::UnorderedElementsAre;
using ::testing::Pair;
...
absl::flat_hash_map<string, int> m = {{"a", 1}, {"b", 2}, {"c", 3}};
EXPECT_THAT(m, UnorderedElementsAre(
Pair("a", 1), Pair("b", 2), Pair("c", 3)));
提示
ElementsAre*()
可以用于匹配任何实现 STL 迭代器模式的容器(即它具有const_iterator
类型并支持begin()/end()
),而不仅仅是 STL 中定义的那些。它甚至可以与尚未编写的容器类型一起使用 - 只要它们遵循上述模式即可。- 你可以使用嵌套的
ElementsAre*()
来匹配嵌套(多维)容器。 - 如果容器是通过指针而不是通过引用传递的,则只需编写
Pointee(ElementsAre*(...))
。 - 对于
ElementsAre*()
来说,元素的顺序很重要。如果将其与元素顺序未定义的容器(例如std::unordered_map
)一起使用,则应使用UnorderedElementsAre
。
共享 Matcher
在底层,gMock matcher 对象由指向引用计数的实现对象的指针组成。允许复制 matcher,并且非常高效,因为仅复制指针。当引用实现对象的最后一个 matcher 消亡时,该实现对象将被删除。
因此,如果你有一些想要一遍又一遍使用的复杂 matcher,则无需每次都构建它。只需将其分配给 matcher 变量并重复使用该变量即可!例如:
using ::testing::AllOf;
using ::testing::Gt;
using ::testing::Le;
using ::testing::Matcher;
...
Matcher<int> in_range = AllOf(Gt(5), Le(10));
... use in_range as a matcher in multiple EXPECT_CALLs ...
Matcher 必须没有副作用
警告:gMock 不保证匹配器何时或被调用多少次。因此,所有匹配器都必须是纯函数式的:它们不能有任何副作用,并且匹配结果不能依赖于匹配器的参数和被匹配值之外的任何东西。
无论匹配器是如何定义的(例如,它是标准匹配器之一,还是自定义匹配器),都必须满足此要求。特别是,匹配器绝不能调用模拟函数,因为那会影响模拟对象和 gMock 的状态。
设置预期
何时期望
ON_CALL
可能是 gMock 中最被低估的构造。
基本上有两种构造用于定义模拟对象的行为:ON_CALL
和 EXPECT_CALL
。 区别? ON_CALL
定义了在调用模拟方法时会发生什么,但不意味着对该方法被调用的任何期望。EXPECT_CALL
不仅定义了行为,还设置了一个期望,即该方法将被调用,使用给定的参数,调用的次数为给定的次数(当您也指定顺序时,也按照给定的顺序)。
既然 EXPECT_CALL
做的事情更多,它是否比 ON_CALL
更好? 并非如此。 每个 EXPECT_CALL
都会对被测代码的行为添加一个约束。 拥有比必要更多的约束是非常糟糕的 - 甚至比约束不足更糟糕。
这可能违反直觉。验证更多的测试怎么会比验证更少的测试更糟糕?验证难道不是测试的全部意义所在吗?
答案在于测试应该验证什么。一个好的测试会验证代码的契约。 如果一个测试过度指定,它就没有给实现留下足够的自由。 结果,在不破坏契约的情况下更改实现(例如,重构和优化),这应该是完全可以的,但可能会破坏这些测试。 然后你必须花时间修复它们,却发现下次实现更改时它们又被破坏了。
请记住,不必在一个测试中验证多个属性。 事实上,在一个测试中只验证一件事是一种好的风格。 如果你这样做,一个错误很可能只会破坏一个或两个测试,而不是几十个(你宁愿调试哪种情况?)。 如果你也有给测试起描述性名称的习惯,告诉测试验证了什么,你通常可以很容易地从测试日志本身猜出问题所在。
所以默认情况下使用 ON_CALL
,并且只有当您确实打算验证调用是否被执行时才使用 EXPECT_CALL
。 例如,您可能在您的测试夹具中有一堆 ON_CALL
来设置同一组中所有测试共享的通用模拟行为,并在不同的 TEST_F
中编写(少量)不同的 EXPECT_CALL
来验证代码行为的不同方面。 与每个 TEST
都有许多 EXPECT_CALL
的风格相比,这会导致测试更能抵抗实现更改(因此不太可能需要维护),并使测试的意图更明显(因此当您确实需要维护它们时,它们更容易维护)。
如果您对在没有 EXPECT_CALL
的情况下调用模拟方法时打印的“Uninteresting mock function call”消息感到困扰,您可以使用 NiceMock
来抑制模拟对象的所有此类消息,或者通过添加 EXPECT_CALL(...).Times(AnyNumber())
来抑制特定方法的消息。 不要通过盲目添加 EXPECT_CALL(...)
来抑制它,否则您将拥有一个难以维护的测试。
忽略不相关的调用
如果您不关心模拟方法的调用方式,只需对它不做任何说明。 在这种情况下,如果该方法被调用,gMock 将执行其默认操作以允许测试程序继续运行。 如果您对 gMock 采取的默认操作不满意,您可以使用 DefaultValue<T>::Set()
(此处描述)或 ON_CALL()
来覆盖它。
请注意,一旦您表达了对特定模拟方法的兴趣(通过 EXPECT_CALL()
),对它的所有调用都必须匹配某些预期。 如果调用了此函数,但参数与任何 EXPECT_CALL()
语句都不匹配,则会出错。
禁止意外调用
如果根本不应该调用模拟方法,请明确说明
using ::testing::_;
...
EXPECT_CALL(foo, Bar(_))
.Times(0);
如果允许对该方法进行某些调用,但不允许其余调用,只需列出所有预期的调用
using ::testing::AnyNumber;
using ::testing::Gt;
...
EXPECT_CALL(foo, Bar(5));
EXPECT_CALL(foo, Bar(Gt(10)))
.Times(AnyNumber());
对 foo.Bar()
的调用与任何 EXPECT_CALL()
语句都不匹配将是一个错误。
理解不相关调用 vs 意外调用
gMock 中,不相关调用和意外调用是不同的概念。 非常不同。
如果甚至没有一个 EXPECT_CALL(x, Y(...))
设置,则调用 x.Y(...)
是不相关的。 换句话说,测试根本不关心 x.Y()
方法,正如测试不关心对它说任何话那样。
如果设置了一些 EXPECT_CALL(x, Y(...))
,但没有一个与该调用匹配,则调用 x.Y(...)
是意外的。 换句话说,测试对 x.Y()
方法感兴趣(因此它显式设置了一些 EXPECT_CALL
来验证它是如何被调用的); 但是,验证失败,因为测试不希望发生此特定调用。
意外调用始终是一个错误,因为被测代码的行为方式与测试期望的行为方式不符。
默认情况下,不相关的调用不是错误,因为它不违反测试指定的任何约束。 (gMock 的理念是不说任何话意味着没有约束。) 但是,它会导致警告,因为它可能表明存在问题(例如,测试作者可能忘记指定约束)。
在 gMock 中,可以使用 NiceMock
和 StrictMock
使模拟类“nice”或“strict”。 这如何影响不相关的调用和意外的调用?
nice 模拟抑制不相关的调用警告。 它比默认模拟更安静,但其他方面相同。 如果测试使用默认模拟失败,则使用 nice 模拟也会失败。 反之亦然。 不要期望使模拟 nice 会改变测试的结果。
strict 模拟将不相关的调用警告转换为错误。 因此,使模拟 strict 可能会更改测试的结果。
让我们看一个例子
TEST(...) {
NiceMock<MockDomainRegistry> mock_registry;
EXPECT_CALL(mock_registry, GetDomainOwner("google.com"))
.WillRepeatedly(Return("Larry Page"));
// Use mock_registry in code under test.
... &mock_registry ...
}
这里唯一的 EXPECT_CALL
表示对 GetDomainOwner()
的所有调用都必须具有 "google.com"
作为参数。 如果调用 GetDomainOwner("yahoo.com")
,它将是一个意外的调用,因此会出错。 拥有一个 nice 模拟并不能改变意外调用的严重性。
那么我们如何告诉 gMock GetDomainOwner()
也可以使用其他参数调用呢? 标准技术是添加一个“catch all” EXPECT_CALL
EXPECT_CALL(mock_registry, GetDomainOwner(_))
.Times(AnyNumber()); // catches all other calls to this method.
EXPECT_CALL(mock_registry, GetDomainOwner("google.com"))
.WillRepeatedly(Return("Larry Page"));
请记住,_
是匹配任何内容的通配符匹配器。 这样,如果调用 GetDomainOwner("google.com")
,它将执行第二个 EXPECT_CALL
所说的操作; 如果使用不同的参数调用它,它将执行第一个 EXPECT_CALL
所说的操作。
请注意,两个 EXPECT_CALL
的顺序很重要,因为较新的 EXPECT_CALL
优先于较旧的 EXPECT_CALL
。
有关不相关的调用、nice 模拟和 strict 模拟的更多信息,请阅读“The Nice, the Strict, and the Naggy”。
忽略不相关的参数
如果你的测试不关心参数(它只关心调用的数量或顺序),你通常可以简单地省略参数列表
// Expect foo.Bar( ... ) twice with any arguments.
EXPECT_CALL(foo, Bar).Times(2);
// Delegate to the given method whenever the factory is invoked.
ON_CALL(foo_factory, MakeFoo)
.WillByDefault(&BuildFooForTest);
只有当方法未重载时,此功能才可用; 为了防止意外行为,尝试对特定重载不明确的方法设置期望是一个编译错误。 您可以通过提供比模拟类提供的更简单的模拟接口来解决此问题。
当参数很有趣但匹配逻辑非常复杂时,此模式也很有用。 您可以保持参数列表未指定,并使用 SaveArg 操作保存值以供以后验证。 如果你这样做,你可以很容易地区分调用方法的次数错误和使用错误的参数调用方法。
期望有序调用
尽管稍后定义的 EXPECT_CALL()
语句在 gMock 尝试将函数调用与期望匹配时具有优先权,但默认情况下调用不必按照编写 EXPECT_CALL()
语句的顺序发生。 例如,如果参数与第二个 EXPECT_CALL()
中的匹配器匹配,但不与第一个和第三个匹配器匹配,则将使用第二个期望。
如果你希望所有调用都按照期望的顺序发生,请将 EXPECT_CALL()
语句放在一个块中,并在其中定义一个类型为 InSequence
的变量。
using ::testing::_;
using ::testing::InSequence;
{
InSequence s;
EXPECT_CALL(foo, DoThis(5));
EXPECT_CALL(bar, DoThat(_))
.Times(2);
EXPECT_CALL(foo, DoThis(6));
}
在这个例子中,我们期望先调用 foo.DoThis(5)
,然后是两次调用 bar.DoThat()
,其中参数可以是任何值,最后调用 foo.DoThis(6)
。如果调用顺序错误,gMock 将报告一个错误。
期望部分有序的调用
有时候,要求所有事情都以预定的顺序发生会导致测试变得脆弱。例如,我们可能关心 A
发生在 B
和 C
之前,但不关心 B
和 C
的相对顺序。在这种情况下,测试应该反映我们的真实意图,而不是过度约束。
gMock 允许你对调用施加任意的 DAG(有向无环图)。表达 DAG 的一种方法是使用 After
子句 的 EXPECT_CALL
。
另一种方法是通过 InSequence()
子句(与 InSequence
类不同),我们从 jMock 2 借用了它。它不如 After()
灵活,但当你有一长串连续调用时,它更方便,因为它不需要你为链中的期望想出不同的名称。以下是它的工作原理:
如果我们将 EXPECT_CALL()
语句视为图中的节点,并且每当 A 必须在 B 之前发生时,都添加从节点 A 到节点 B 的边,我们就可以得到一个 DAG。我们使用术语“序列”来表示此 DAG 中的有向路径。现在,如果我们将 DAG 分解为序列,我们只需要知道每个 EXPECT_CALL()
属于哪些序列,以便能够重建原始 DAG。
因此,要指定期望的部分顺序,我们需要做两件事:首先定义一些 Sequence
对象,然后对于每个 EXPECT_CALL()
,说明它是哪个 Sequence
对象的一部分。
同一序列中的期望必须按照它们被编写的顺序发生。例如,
using ::testing::Sequence;
...
Sequence s1, s2;
EXPECT_CALL(foo, A())
.InSequence(s1, s2);
EXPECT_CALL(bar, B())
.InSequence(s1);
EXPECT_CALL(bar, C())
.InSequence(s2);
EXPECT_CALL(foo, D())
.InSequence(s2);
指定了以下 DAG(其中 s1
是 A -> B
,s2
是 A -> C -> D
)
+---> B
|
A ---|
|
+---> C ---> D
这意味着 A 必须发生在 B 和 C 之前,而 C 必须发生在 D 之前。除了这些之外,没有其他顺序限制。
控制期望何时退役
当一个 mock 方法被调用时,gMock 只考虑仍然活跃的期望。期望在创建时是活跃的,并且当必须稍后发生的调用已经发生时,它会变得不活跃(也称为 *退役*)。例如,在
using ::testing::_;
using ::testing::Sequence;
...
Sequence s1, s2;
EXPECT_CALL(log, Log(WARNING, _, "File too large.")) // #1
.Times(AnyNumber())
.InSequence(s1, s2);
EXPECT_CALL(log, Log(WARNING, _, "Data set is empty.")) // #2
.InSequence(s1);
EXPECT_CALL(log, Log(WARNING, _, "User not found.")) // #3
.InSequence(s2);
一旦 #2 或 #3 匹配,#1 将退役。如果在此之后记录了警告 "File too large."
,这将是一个错误。
请注意,期望不会在饱和时自动退役。例如,
using ::testing::_;
...
EXPECT_CALL(log, Log(WARNING, _, _)); // #1
EXPECT_CALL(log, Log(WARNING, _, "File too large.")); // #2
表示将只有一个带有消息 "File too large."
的警告。如果第二个警告也包含此消息,则 #2 将再次匹配并导致违反上限的错误。
如果这不是你想要的,你可以要求一个期望在饱和时立即退役
using ::testing::_;
...
EXPECT_CALL(log, Log(WARNING, _, _)); // #1
EXPECT_CALL(log, Log(WARNING, _, "File too large.")) // #2
.RetiresOnSaturation();
这里 #2 只能使用一次,所以如果你有两个带有消息 "File too large."
的警告,第一个将匹配 #2,第二个将匹配 #1 - 不会发生错误。
使用 Actions
从 Mock 方法返回引用
如果 mock 函数的返回类型是一个引用,你需要使用 ReturnRef()
而不是 Return()
来返回结果
using ::testing::ReturnRef;
class MockFoo : public Foo {
public:
MOCK_METHOD(Bar&, GetBar, (), (override));
};
...
MockFoo foo;
Bar bar;
EXPECT_CALL(foo, GetBar())
.WillOnce(ReturnRef(bar));
...
从 Mock 方法返回实时值
Return(x)
action 在创建 action 时保存 x
的副本,并在每次执行时始终返回相同的值。有时你可能希望返回 x
的 *实时* 值(即在 action *执行* 时它的值)。为此,请使用 ReturnRef()
或 ReturnPointee()
。
如果 mock 函数的返回类型是一个引用,你可以使用 ReturnRef(x)
来完成它,如前面的配方(“从 Mock 方法返回引用”)所示。但是,gMock 不允许你在返回类型不是引用的 mock 函数中使用 ReturnRef()
,因为这样做通常表明用户错误。那么,你应该怎么做?
虽然你可能会很想这样做,但不要使用 std::ref()
using ::testing::Return;
class MockFoo : public Foo {
public:
MOCK_METHOD(int, GetValue, (), (override));
};
...
int x = 0;
MockFoo foo;
EXPECT_CALL(foo, GetValue())
.WillRepeatedly(Return(std::ref(x))); // Wrong!
x = 42;
EXPECT_EQ(foo.GetValue(), 42);
不幸的是,它在这里不起作用。上面的代码将失败并显示错误
Value of: foo.GetValue()
Actual: 0
Expected: 42
原因是 Return(*value*)
在 action *创建* 时,而不是在它 *执行* 时,将 value
转换为 mock 函数的实际返回类型。(选择此行为是为了在 value
是引用一些临时对象的代理对象时,action 是安全的。)因此,在设置期望时,std::ref(x)
将被转换为 int
值(而不是 const int&
),并且 Return(std::ref(x))
将始终返回 0。
提供 ReturnPointee(pointer)
专门是为了解决这个问题。它返回在 action *执行* 时由 pointer
指向的值
using ::testing::ReturnPointee;
...
int x = 0;
MockFoo foo;
EXPECT_CALL(foo, GetValue())
.WillRepeatedly(ReturnPointee(&x)); // Note the & here.
x = 42;
EXPECT_EQ(foo.GetValue(), 42); // This will succeed now.
组合 Actions
想在调用函数时做不止一件事?没问题。DoAll()
允许你每次执行一系列 actions。只会使用序列中最后一个 action 的返回值。
using ::testing::_;
using ::testing::DoAll;
class MockFoo : public Foo {
public:
MOCK_METHOD(bool, Bar, (int n), (override));
};
...
EXPECT_CALL(foo, Bar(_))
.WillOnce(DoAll(action_1,
action_2,
...
action_n));
最后一个 action 的返回值**必须**与 mock 方法的返回类型匹配。在上面的例子中,action_n
可以是 Return(true)
,或者是一个返回 bool
的 lambda,但不能是返回 void
的 SaveArg
。否则,DoAll
的签名将与 WillOnce
期望的签名不匹配,而 WillOnce
是 mock 方法的签名,并且它不会编译。
验证复杂参数
如果你想验证一个方法是用一个特定的参数调用的,但匹配标准很复杂,那么很难区分基数失败(以错误的次数调用该方法)和参数匹配失败。同样,如果你正在匹配多个参数,可能不容易区分哪个参数匹配失败。例如
// Not ideal: this could fail because of a problem with arg1 or arg2, or maybe
// just the method wasn't called.
EXPECT_CALL(foo, SendValues(_, ElementsAre(1, 4, 4, 7), EqualsProto( ... )));
你可以改为保存参数并单独测试它们
EXPECT_CALL(foo, SendValues)
.WillOnce(DoAll(SaveArg<1>(&actual_array), SaveArg<2>(&actual_proto)));
... run the test
EXPECT_THAT(actual_array, ElementsAre(1, 4, 4, 7));
EXPECT_THAT(actual_proto, EqualsProto( ... ));
Mock 副作用
有时,一个方法不是通过返回值,而是通过副作用来表现其效果。例如,它可能会改变一些全局状态或修改一个输出参数。要 mock 副作用,通常你可以通过实现 ::testing::ActionInterface
来定义你自己的 action。
如果你只需要更改一个输出参数,内置的 SetArgPointee()
action 很方便
using ::testing::_;
using ::testing::SetArgPointee;
class MockMutator : public Mutator {
public:
MOCK_METHOD(void, Mutate, (bool mutate, int* value), (override));
...
}
...
MockMutator mutator;
EXPECT_CALL(mutator, Mutate(true, _))
.WillOnce(SetArgPointee<1>(5));
在这个例子中,当 mutator.Mutate()
被调用时,我们将把 5 赋值给由参数 #1(基于 0)指向的 int
变量。
SetArgPointee()
方便地创建你传递给它的值的内部副本,从而无需将该值保持在作用域内并保持活动状态。然而,这意味着该值必须具有复制构造函数和赋值运算符。
如果 mock 方法还需要返回一个值,你可以使用 DoAll()
将 SetArgPointee()
与 Return()
链接起来,记住将 Return()
语句放在最后
using ::testing::_;
using ::testing::DoAll;
using ::testing::Return;
using ::testing::SetArgPointee;
class MockMutator : public Mutator {
public:
...
MOCK_METHOD(bool, MutateInt, (int* value), (override));
}
...
MockMutator mutator;
EXPECT_CALL(mutator, MutateInt(_))
.WillOnce(DoAll(SetArgPointee<0>(5),
Return(true)));
但是请注意,如果你使用 ReturnOKWith()
方法,它将覆盖函数调用响应参数中 SetArgPointee()
提供的值。
如果输出参数是一个数组,请改用 SetArrayArgument<N>(first, last)
action。它将源范围 [first, last)
中的元素复制到由第 N
个(基于 0)参数指向的数组中
using ::testing::NotNull;
using ::testing::SetArrayArgument;
class MockArrayMutator : public ArrayMutator {
public:
MOCK_METHOD(void, Mutate, (int* values, int num_values), (override));
...
}
...
MockArrayMutator mutator;
int values[5] = {1, 2, 3, 4, 5};
EXPECT_CALL(mutator, Mutate(NotNull(), 5))
.WillOnce(SetArrayArgument<0>(values, values + 5));
当参数是一个输出迭代器时,这也有效
using ::testing::_;
using ::testing::SetArrayArgument;
class MockRolodex : public Rolodex {
public:
MOCK_METHOD(void, GetNames, (std::back_insert_iterator<vector<string>>),
(override));
...
}
...
MockRolodex rolodex;
vector<string> names = {"George", "John", "Thomas"};
EXPECT_CALL(rolodex, GetNames(_))
.WillOnce(SetArrayArgument<0>(names.begin(), names.end()));
根据状态更改 Mock 对象的行为
如果你期望一个调用会改变 mock 对象的行为,你可以使用 ::testing::InSequence
来指定调用前后的不同行为
using ::testing::InSequence;
using ::testing::Return;
...
{
InSequence seq;
EXPECT_CALL(my_mock, IsDirty())
.WillRepeatedly(Return(true));
EXPECT_CALL(my_mock, Flush());
EXPECT_CALL(my_mock, IsDirty())
.WillRepeatedly(Return(false));
}
my_mock.FlushIfDirty();
这使得 my_mock.IsDirty()
在 my_mock.Flush()
被调用之前返回 true
,之后返回 false
。
如果行为更改更复杂,您可以将效果存储在一个变量中,并使模拟方法从该变量获取其返回值
using ::testing::_;
using ::testing::SaveArg;
using ::testing::Return;
ACTION_P(ReturnPointee, p) { return *p; }
...
int previous_value = 0;
EXPECT_CALL(my_mock, GetPrevValue)
.WillRepeatedly(ReturnPointee(&previous_value));
EXPECT_CALL(my_mock, UpdateValue)
.WillRepeatedly(SaveArg<0>(&previous_value));
my_mock.DoSomethingToUpdateValue();
这里,my_mock.GetPrevValue()
将始终返回上一次 UpdateValue()
调用的参数。
为返回值类型设置默认值
如果模拟方法的返回类型是内置的 C++ 类型或指针,则默认情况下,在调用时它将返回 0。此外,在 C++ 11 及更高版本中,返回类型具有默认构造函数的模拟方法默认情况下将返回默认构造的值。只有当此默认值不适合您时,才需要指定操作。
有时,您可能想要更改此默认值,或者您可能想要为 gMock 不知道的类型指定默认值。您可以使用 ::testing::DefaultValue
类模板来做到这一点
using ::testing::DefaultValue;
class MockFoo : public Foo {
public:
MOCK_METHOD(Bar, CalculateBar, (), (override));
};
...
Bar default_bar;
// Sets the default return value for type Bar.
DefaultValue<Bar>::Set(default_bar);
MockFoo foo;
// We don't need to specify an action here, as the default
// return value works for us.
EXPECT_CALL(foo, CalculateBar());
foo.CalculateBar(); // This should return default_bar.
// Unsets the default return value.
DefaultValue<Bar>::Clear();
请注意,更改类型的默认值可能会使您的测试难以理解。 我们建议您谨慎使用此功能。 例如,您可能希望确保 Set()
和 Clear()
调用紧挨着使用您的 mock 的代码。
为 Mock 方法设置默认操作
您已经了解了如何更改给定类型的默认值。 但是,这对于您的目的来说可能太粗糙了:也许您有两个具有相同返回类型的 mock 方法,并且您希望它们具有不同的行为。 ON_CALL()
宏允许您在方法级别自定义 mock 的行为
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Gt;
using ::testing::Return;
...
ON_CALL(foo, Sign(_))
.WillByDefault(Return(-1));
ON_CALL(foo, Sign(0))
.WillByDefault(Return(0));
ON_CALL(foo, Sign(Gt(0)))
.WillByDefault(Return(1));
EXPECT_CALL(foo, Sign(_))
.Times(AnyNumber());
foo.Sign(5); // This should return 1.
foo.Sign(-9); // This should return -1.
foo.Sign(0); // This should return 0.
您可能已经猜到,当有多个 ON_CALL()
语句时,顺序较新的语句优先于较旧的语句。 换句话说,将使用与函数参数匹配的**最后**一个语句。 这种匹配顺序允许您在 mock 对象的构造函数或测试 fixture 的设置阶段设置通用行为,并在以后专门化 mock 的行为。
请注意,ON_CALL
和 EXPECT_CALL
都具有相同的“后面的语句优先”规则,但它们不相互作用。 也就是说,EXPECT_CALL
具有与 ON_CALL
优先级顺序不同的优先级顺序。
使用函数/方法/仿函数/Lambda 作为操作
如果内置操作不适合您,则可以使用现有的可调用对象(函数、std::function
、方法、仿函数、lambda)作为操作。
using ::testing::_; using ::testing::Invoke;
class MockFoo : public Foo {
public:
MOCK_METHOD(int, Sum, (int x, int y), (override));
MOCK_METHOD(bool, ComplexJob, (int x), (override));
};
int CalculateSum(int x, int y) { return x + y; }
int Sum3(int x, int y, int z) { return x + y + z; }
class Helper {
public:
bool ComplexJob(int x);
};
...
MockFoo foo;
Helper helper;
EXPECT_CALL(foo, Sum(_, _))
.WillOnce(&CalculateSum)
.WillRepeatedly(Invoke(NewPermanentCallback(Sum3, 1)));
EXPECT_CALL(foo, ComplexJob(_))
.WillOnce(Invoke(&helper, &Helper::ComplexJob))
.WillOnce([] { return true; })
.WillRepeatedly([](int x) { return x > 0; });
foo.Sum(5, 6); // Invokes CalculateSum(5, 6).
foo.Sum(2, 3); // Invokes Sum3(1, 2, 3).
foo.ComplexJob(10); // Invokes helper.ComplexJob(10).
foo.ComplexJob(-1); // Invokes the inline lambda.
唯一的要求是函数等的类型必须与 mock 函数的签名兼容,这意味着后者的参数(如果需要)可以隐式转换为前者的相应参数,并且前者的返回类型可以隐式转换为后者的返回类型。因此,您可以调用类型不与 mock 函数完全相同的对象,只要这样做是安全的 - 很好,不是吗?
请注意
- 该操作拥有回调的所有权,并在操作本身被销毁时删除它。
-
如果回调的类型派生自基本回调类型
C
,则需要将其隐式转换为C
才能解决重载问题,例如using ::testing::Invoke; ... ResultCallback<bool>* is_ok = ...; ... Invoke(is_ok) ...; // This works. BlockingClosure* done = new BlockingClosure; ... Invoke(implicit_cast<Closure*>(done)) ...; // The cast is necessary.
使用带有额外信息的函数作为操作
您使用 Invoke()
调用的函数或仿函数必须具有与您用于它的 mock 函数相同数量的参数。 有时您可能有一个接受更多参数的函数,并且您愿意自己传入额外的参数来填补空白。 您可以使用带有预绑定参数的回调在 gMock 中执行此操作。 这是一个例子
using ::testing::Invoke;
class MockFoo : public Foo {
public:
MOCK_METHOD(char, DoThis, (int n), (override));
};
char SignOfSum(int x, int y) {
const int sum = x + y;
return (sum > 0) ? '+' : (sum < 0) ? '-' : '0';
}
TEST_F(FooTest, Test) {
MockFoo foo;
EXPECT_CALL(foo, DoThis(2))
.WillOnce(Invoke(NewPermanentCallback(SignOfSum, 5)));
EXPECT_EQ(foo.DoThis(2), '+'); // Invokes SignOfSum(5, 2).
}
调用没有参数的函数/方法/仿函数/Lambda/回调
Invoke()
将 mock 函数的参数传递给正在调用的函数等,以便被调用者具有完整的调用上下文以进行处理。 如果被调用的函数对某些或所有参数不感兴趣,则可以简单地忽略它们。
然而,一个常见的模式是,测试作者想要调用一个没有 mock 函数参数的函数。 她可以使用一个包装函数,该函数在调用下划线空函数之前丢弃参数。 不用说,这可能很乏味并且模糊了测试的意图。
有两种解决方案可以解决此问题。 首先,您可以传递任何零参数的可调用对象作为操作。 或者,使用 InvokeWithoutArgs()
,它类似于 Invoke()
,只是它不会将被调用 mock 函数的参数传递给调用者。 这是每个人的一个例子
using ::testing::_;
using ::testing::InvokeWithoutArgs;
class MockFoo : public Foo {
public:
MOCK_METHOD(bool, ComplexJob, (int n), (override));
};
bool Job1() { ... }
bool Job2(int n, char c) { ... }
...
MockFoo foo;
EXPECT_CALL(foo, ComplexJob(_))
.WillOnce([] { Job1(); });
.WillOnce(InvokeWithoutArgs(NewPermanentCallback(Job2, 5, 'a')));
foo.ComplexJob(10); // Invokes Job1().
foo.ComplexJob(20); // Invokes Job2(5, 'a').
请注意
- 该操作拥有回调的所有权,并在操作本身被销毁时删除它。
-
如果回调的类型派生自基本回调类型
C
,则需要将其隐式转换为C
才能解决重载问题,例如using ::testing::InvokeWithoutArgs; ... ResultCallback<bool>* is_ok = ...; ... InvokeWithoutArgs(is_ok) ...; // This works. BlockingClosure* done = ...; ... InvokeWithoutArgs(implicit_cast<Closure*>(done)) ...; // The cast is necessary.
调用 Mock 函数的参数
有时,mock 函数会收到一个函数指针,一个仿函数(换句话说,一个“可调用对象”)作为参数,例如
class MockFoo : public Foo {
public:
MOCK_METHOD(bool, DoThis, (int n, (ResultCallback1<bool, int>* callback)),
(override));
};
您可能想要调用此可调用参数
using ::testing::_;
...
MockFoo foo;
EXPECT_CALL(foo, DoThis(_, _))
.WillOnce(...);
// Will execute callback->Run(5), where callback is the
// second argument DoThis() receives.
注意:以下部分是 C++ 拥有 lambdas 之前的遗留文档
Arghh,您需要引用一个 mock 函数参数,但 C++ 没有 lambda(还没有),所以您必须定义自己的操作。 :-( 还是真的吗?
好吧,gMock 有一个操作来完全解决这个问题
InvokeArgument<N>(arg_1, arg_2, ..., arg_m)
将使用 arg_1
、arg_2
、… 和 arg_m
调用 mock 函数接收的第 N
个(基于 0)参数。 无论参数是函数指针、仿函数还是回调。 gMock 处理所有这些。
这样,您可以编写
using ::testing::_;
using ::testing::InvokeArgument;
...
EXPECT_CALL(foo, DoThis(_, _))
.WillOnce(InvokeArgument<1>(5));
// Will execute callback->Run(5), where callback is the
// second argument DoThis() receives.
如果可调用对象通过引用获取参数怎么办? 没问题 - 只需将其包装在 std::ref()
中
...
MOCK_METHOD(bool, Bar,
((ResultCallback2<bool, int, const Helper&>* callback)),
(override));
...
using ::testing::_;
using ::testing::InvokeArgument;
...
MockFoo foo;
Helper helper;
...
EXPECT_CALL(foo, Bar(_))
.WillOnce(InvokeArgument<0>(5, std::ref(helper)));
// std::ref(helper) guarantees that a reference to helper, not a copy of
// it, will be passed to the callback.
如果可调用对象通过引用获取参数并且我们不将参数包装在 std::ref()
中怎么办? 然后 InvokeArgument()
将复制该参数,并将对副本的引用传递给可调用对象,而不是对原始值的引用。 当参数是临时值时,这尤其方便
...
MOCK_METHOD(bool, DoThat, (bool (*f)(const double& x, const string& s)),
(override));
...
using ::testing::_;
using ::testing::InvokeArgument;
...
MockFoo foo;
...
EXPECT_CALL(foo, DoThat(_))
.WillOnce(InvokeArgument<0>(5.0, string("Hi")));
// Will execute (*f)(5.0, string("Hi")), where f is the function pointer
// DoThat() receives. Note that the values 5.0 and string("Hi") are
// temporary and dead once the EXPECT_CALL() statement finishes. Yet
// it's fine to perform this action later, since a copy of the values
// are kept inside the InvokeArgument action.
忽略操作的结果
有时您有一个返回某些内容的操作,但您需要一个返回 void
的操作(也许您想在返回 void
的 mock 函数中使用它,或者它需要在 DoAll()
中使用并且它不是列表中的最后一个)。 IgnoreResult()
允许您这样做。 例如
using ::testing::_;
using ::testing::DoAll;
using ::testing::IgnoreResult;
using ::testing::Return;
int Process(const MyData& data);
string DoSomething();
class MockFoo : public Foo {
public:
MOCK_METHOD(void, Abc, (const MyData& data), (override));
MOCK_METHOD(bool, Xyz, (), (override));
};
...
MockFoo foo;
EXPECT_CALL(foo, Abc(_))
// .WillOnce(Invoke(Process));
// The above line won't compile as Process() returns int but Abc() needs
// to return void.
.WillOnce(IgnoreResult(Process));
EXPECT_CALL(foo, Xyz())
.WillOnce(DoAll(IgnoreResult(DoSomething),
// Ignores the string DoSomething() returns.
Return(true)));
请注意,您不能在已经返回 void
的操作上使用 IgnoreResult()
。 这样做会导致丑陋的编译器错误。
选择操作的参数
假设您有一个接受七个参数的 mock 函数 Foo()
,并且您有一个自定义操作,您希望在调用 Foo()
时调用该操作。 问题是,自定义操作只需要三个参数
using ::testing::_;
using ::testing::Invoke;
...
MOCK_METHOD(bool, Foo,
(bool visible, const string& name, int x, int y,
(const map<pair<int, int>>), double& weight, double min_weight,
double max_wight));
...
bool IsVisibleInQuadrant1(bool visible, int x, int y) {
return visible && x >= 0 && y >= 0;
}
...
EXPECT_CALL(mock, Foo)
.WillOnce(Invoke(IsVisibleInQuadrant1)); // Uh, won't compile. :-(
为了取悦编译器之神,您需要定义一个与 Foo()
具有相同签名并使用正确的参数调用自定义操作的“适配器”
using ::testing::_;
using ::testing::Invoke;
...
bool MyIsVisibleInQuadrant1(bool visible, const string& name, int x, int y,
const map<pair<int, int>, double>& weight,
double min_weight, double max_wight) {
return IsVisibleInQuadrant1(visible, x, y);
}
...
EXPECT_CALL(mock, Foo)
.WillOnce(Invoke(MyIsVisibleInQuadrant1)); // Now it works.
但这不是很尴尬吗?
gMock 提供了一个通用的操作适配器,因此您可以将时间花在比编写自己的适配器更重要的事情上。 这是语法
WithArgs<N1, N2, ..., Nk>(action)
创建一个操作,该操作将给定索引(基于 0)处的 mock 函数的参数传递给内部 action
并执行它。 使用 WithArgs
,我们的原始示例可以写成
using ::testing::_;
using ::testing::Invoke;
using ::testing::WithArgs;
...
EXPECT_CALL(mock, Foo)
.WillOnce(WithArgs<0, 2, 3>(Invoke(IsVisibleInQuadrant1))); // No need to define your own adaptor.
为了更好的可读性,gMock 还为您提供
- 当内部
action
不接受任何参数时,使用WithoutArgs(action)
,以及 - 当内部
action
接受一个参数时,使用WithArg<N>(action)
(Arg
后面没有s
)。
您可能已经意识到,InvokeWithoutArgs(...)
只是 WithoutArgs(Invoke(...))
的语法糖。
以下是更多提示
WithArgs
及其朋友中使用的内部操作不必是Invoke()
- 它可以是任何东西。- 如果需要,您可以在参数列表中重复一个参数,例如
WithArgs<2, 3, 3, 5>(...)
。 - 您可以更改参数的顺序,例如
WithArgs<3, 2, 1>(...)
。 - 所选参数的类型不必与内部操作的签名完全匹配。 只要它们可以隐式转换为内部操作的相应参数,它就可以工作。 例如,如果 mock 函数的第 4 个参数是
int
并且my_action
接受double
,则WithArg<4>(my_action)
将起作用。
忽略操作函数中的参数
selecting-an-action’s-arguments 食谱向我们展示了一种使 mock 函数和具有不兼容参数列表的操作组合在一起的方法。 缺点是,对于编写测试的人来说,将操作包装在 WithArgs<...>()
中可能会很乏味。
如果您定义了一个函数(或方法、仿函数、lambda、回调)与 Invoke*()
一起使用,并且您对它的某些参数不感兴趣,那么除了 WithArgs
之外,还可以将不感兴趣的参数声明为 Unused
。 这样可以使定义不那么混乱,并且在不感兴趣的参数类型发生更改时不太脆弱。 它还可以增加操作函数可以重用的机会。 例如,给定
public:
MOCK_METHOD(double, Foo, double(const string& label, double x, double y),
(override));
MOCK_METHOD(double, Bar, (int index, double x, double y), (override));
而不是
using ::testing::_;
using ::testing::Invoke;
double DistanceToOriginWithLabel(const string& label, double x, double y) {
return sqrt(x*x + y*y);
}
double DistanceToOriginWithIndex(int index, double x, double y) {
return sqrt(x*x + y*y);
}
...
EXPECT_CALL(mock, Foo("abc", _, _))
.WillOnce(Invoke(DistanceToOriginWithLabel));
EXPECT_CALL(mock, Bar(5, _, _))
.WillOnce(Invoke(DistanceToOriginWithIndex));
您可以写
using ::testing::_;
using ::testing::Invoke;
using ::testing::Unused;
double DistanceToOrigin(Unused, double x, double y) {
return sqrt(x*x + y*y);
}
...
EXPECT_CALL(mock, Foo("abc", _, _))
.WillOnce(Invoke(DistanceToOrigin));
EXPECT_CALL(mock, Bar(5, _, _))
.WillOnce(Invoke(DistanceToOrigin));
共享操作
就像匹配器一样,gMock 操作对象由指向引用计数实现的对象的指针组成。 因此,也允许复制操作,并且非常高效。 当引用实现对象的最后一个操作死亡时,实现对象将被删除。
如果您有一些想要一遍又一遍使用的复杂操作,您可能不必每次都从头开始构建它。 如果该操作没有内部状态(即,无论调用多少次,它总是做同样的事情),您可以将其分配给一个操作变量并重复使用该变量。 例如
using ::testing::Action;
using ::testing::DoAll;
using ::testing::Return;
using ::testing::SetArgPointee;
...
Action<bool(int*)> set_flag = DoAll(SetArgPointee<0>(5),
Return(true));
... use set_flag in .WillOnce() and .WillRepeatedly() ...
然而,如果 action 拥有自己的状态,那么共享 action 对象可能会让你感到惊讶。假设你有一个 action 工厂 IncrementCounter(init)
,它创建一个 action,该 action 递增并返回一个计数器,计数器的初始值为 init
。使用从相同表达式创建的两个 action,并且使用共享 action 将会展示出不同的行为。例如
EXPECT_CALL(foo, DoThis())
.WillRepeatedly(IncrementCounter(0));
EXPECT_CALL(foo, DoThat())
.WillRepeatedly(IncrementCounter(0));
foo.DoThis(); // Returns 1.
foo.DoThis(); // Returns 2.
foo.DoThat(); // Returns 1 - DoThat() uses a different
// counter than DoThis()'s.
对比
using ::testing::Action;
...
Action<int()> increment = IncrementCounter(0);
EXPECT_CALL(foo, DoThis())
.WillRepeatedly(increment);
EXPECT_CALL(foo, DoThat())
.WillRepeatedly(increment);
foo.DoThis(); // Returns 1.
foo.DoThis(); // Returns 2.
foo.DoThat(); // Returns 3 - the counter is shared.
测试异步行为
gMock 经常遇到的一个问题是,测试异步行为可能很困难。假设你有一个 EventQueue
类,你想要测试它,并且你创建了一个单独的 EventDispatcher
接口,以便你可以轻松地对其进行 mock。但是,该类的实现是在后台线程上触发所有事件,这使得测试时序变得困难。你可以简单地插入 sleep()
语句并希望一切顺利,但这会使你的测试行为不确定。一个更好的方法是使用 gMock action 和 Notification
对象来强制你的异步测试表现为同步。
class MockEventDispatcher : public EventDispatcher {
MOCK_METHOD(bool, DispatchEvent, (int32), (override));
};
TEST(EventQueueTest, EnqueueEventTest) {
MockEventDispatcher mock_event_dispatcher;
EventQueue event_queue(&mock_event_dispatcher);
const int32 kEventId = 321;
absl::Notification done;
EXPECT_CALL(mock_event_dispatcher, DispatchEvent(kEventId))
.WillOnce([&done] { done.Notify(); });
event_queue.EnqueueEvent(kEventId);
done.WaitForNotification();
}
在上面的例子中,我们设置了正常的 gMock 预期,然后添加了一个额外的 action 来通知 Notification
对象。现在我们只需在主线程中调用 Notification::WaitForNotification()
来等待异步调用完成。之后,我们的测试套件就完成了,我们可以安全地退出。
注意:这个例子有一个缺点:即,如果预期没有得到满足,我们的测试将永远运行下去。它最终会超时并失败,但会花费更长的时间,并且调试起来会稍微困难一些。为了缓解这个问题,你可以使用 WaitForNotificationWithTimeout(ms)
代替 WaitForNotification()
。
使用 gMock 的其他技巧
Mock 使用仅移动类型 (Move-Only Types) 的方法
C++11 引入了仅移动类型。仅移动类型的 value 可以从一个对象移动到另一个对象,但不能复制。std::unique_ptr<T>
可能是最常用的仅移动类型。
Mock 接受和/或返回仅移动类型的方法会带来一些挑战,但并非无法克服。本技巧将向你展示如何做到这一点。请注意,仅移动方法参数的支持仅在 2017 年 4 月引入到 gMock 中;在较旧的代码中,你可能会发现更复杂的 解决方法,因为缺少此功能。
假设我们正在开发一个虚构的项目,该项目允许人们发布和分享名为“buzz”的代码片段。你的代码使用以下类型
enum class AccessLevel { kInternal, kPublic };
class Buzz {
public:
explicit Buzz(AccessLevel access) { ... }
...
};
class Buzzer {
public:
virtual ~Buzzer() {}
virtual std::unique_ptr<Buzz> MakeBuzz(StringPiece text) = 0;
virtual bool ShareBuzz(std::unique_ptr<Buzz> buzz, int64_t timestamp) = 0;
...
};
Buzz
对象表示正在发布的代码片段。实现 Buzzer
接口的类能够创建和分享 Buzz
es。Buzzer
中的方法可能会返回 unique_ptr<Buzz>
或接受 unique_ptr<Buzz>
。现在我们需要在测试中 mock Buzzer
。
要 mock 一个接受或返回仅移动类型的方法,你只需像往常一样使用熟悉的 MOCK_METHOD
语法
class MockBuzzer : public Buzzer {
public:
MOCK_METHOD(std::unique_ptr<Buzz>, MakeBuzz, (StringPiece text), (override));
MOCK_METHOD(bool, ShareBuzz, (std::unique_ptr<Buzz> buzz, int64_t timestamp),
(override));
};
现在我们已经定义了 mock 类,我们可以在测试中使用它。在以下代码示例中,我们假设我们已经定义了一个名为 mock_buzzer_
的 MockBuzzer
对象
MockBuzzer mock_buzzer_;
首先,让我们看看如何设置对 MakeBuzz()
方法的预期,该方法返回 unique_ptr<Buzz>
。
像往常一样,如果你设置了一个没有 action 的预期(即 .WillOnce()
或 .WillRepeatedly()
子句),当该预期被触发时,将采取该方法的默认 action。由于 unique_ptr<>
有一个返回 null unique_ptr
的默认构造函数,如果你不指定 action,你将会得到这个。
using ::testing::IsNull;
...
// Use the default action.
EXPECT_CALL(mock_buzzer_, MakeBuzz("hello"));
// Triggers the previous EXPECT_CALL.
EXPECT_THAT(mock_buzzer_.MakeBuzz("hello"), IsNull());
如果你对默认 action 不满意,你可以像往常一样对其进行调整;请参阅 设置默认 Action。
如果你只需要返回一个仅移动的 value,你可以将它与 WillOnce
结合使用。例如
EXPECT_CALL(mock_buzzer_, MakeBuzz("hello"))
.WillOnce(Return(std::make_unique<Buzz>(AccessLevel::kInternal)));
EXPECT_NE(nullptr, mock_buzzer_.MakeBuzz("hello"));
测验时间!你认为如果 Return
action 被执行多次会发生什么(例如,你编写了 ... .WillRepeatedly(Return(std::move(...)));
)?仔细想想,在 action 第一次运行后,源 value 将被消耗(因为它是一个仅移动的 value),所以下一次,没有 value 可以移动了 – 你将得到一个运行时错误,即 Return(std::move(...))
只能运行一次。
如果你需要你的 mock 方法做更多的事情,而不仅仅是移动一个预定义的 value,请记住你总是可以使用 lambda 或可调用对象,它们几乎可以做任何你想做的事情
EXPECT_CALL(mock_buzzer_, MakeBuzz("x"))
.WillRepeatedly([](StringPiece text) {
return std::make_unique<Buzz>(AccessLevel::kInternal);
});
EXPECT_NE(nullptr, mock_buzzer_.MakeBuzz("x"));
EXPECT_NE(nullptr, mock_buzzer_.MakeBuzz("x"));
每次触发这个 EXPECT_CALL
时,都会创建一个新的 unique_ptr<Buzz>
并返回。你不能使用 Return(std::make_unique<...>(...))
来做到这一点。
以上涵盖了返回仅移动 value;但是我们如何处理接受仅移动参数的方法呢?答案是它们正常工作,尽管当任何方法的参数是仅移动的时,某些 action 将无法编译。你总是可以使用 Return
,或者一个 lambda 或仿函数
using ::testing::Unused;
EXPECT_CALL(mock_buzzer_, ShareBuzz(NotNull(), _)).WillOnce(Return(true));
EXPECT_TRUE(mock_buzzer_.ShareBuzz(std::make_unique<Buzz>(AccessLevel::kInternal)),
0);
EXPECT_CALL(mock_buzzer_, ShareBuzz(_, _)).WillOnce(
[](std::unique_ptr<Buzz> buzz, Unused) { return buzz != nullptr; });
EXPECT_FALSE(mock_buzzer_.ShareBuzz(nullptr, 0));
许多内置 action(WithArgs
、WithoutArgs
、DeleteArg
、SaveArg
, …)原则上可以支持仅移动参数,但尚未实现对此的支持。如果这阻碍了你,请提交 bug。
一些 action(例如,DoAll
)在内部复制它们的参数,因此它们永远无法使用不可复制的对象;你必须改用仿函数。
仅移动类型的遗留解决方法
仅移动函数参数的支持仅在 2017 年 4 月引入到 gMock 中。在较旧的代码中,你可能会遇到以下解决方法,以解决缺少此功能的问题(它不再必要 - 我们仅将其包含在内以供参考)
class MockBuzzer : public Buzzer {
public:
MOCK_METHOD(bool, DoShareBuzz, (Buzz* buzz, Time timestamp));
bool ShareBuzz(std::unique_ptr<Buzz> buzz, Time timestamp) override {
return DoShareBuzz(buzz.get(), timestamp);
}
};
诀窍是将 ShareBuzz()
方法委托给一个不接受仅移动参数的 mock 方法(我们称之为 DoShareBuzz()
)。然后,你不是设置对 ShareBuzz()
的预期,而是设置对 DoShareBuzz()
mock 方法的预期。
MockBuzzer mock_buzzer_;
EXPECT_CALL(mock_buzzer_, DoShareBuzz(NotNull(), _));
// When one calls ShareBuzz() on the MockBuzzer like this, the call is
// forwarded to DoShareBuzz(), which is mocked. Therefore this statement
// will trigger the above EXPECT_CALL.
mock_buzzer_.ShareBuzz(std::make_unique<Buzz>(AccessLevel::kInternal), 0);
加快编译速度
信不信由你,编译 mock 类所花费的绝大部分时间都用于生成其构造函数和析构函数,因为它们执行非平凡的任务(例如,验证预期)。更重要的是,具有不同签名mock方法具有不同的类型,因此它们的构造函数/析构函数需要由编译器单独生成。因此,如果你mock许多不同类型的方法,则编译mock类可能会变得非常慢。
如果你遇到编译速度慢的问题,可以将 mock 类的构造函数和析构函数的定义从类主体移到 .cc
文件中。这样,即使你在 N 个文件中 #include
你的 mock 类,编译器也只需要生成一次其构造函数和析构函数,从而大大加快编译速度。
让我们用一个例子来说明这个想法。这是应用此技巧之前的 mock 类的定义
// File mock_foo.h.
...
class MockFoo : public Foo {
public:
// Since we don't declare the constructor or the destructor,
// the compiler will generate them in every translation unit
// where this mock class is used.
MOCK_METHOD(int, DoThis, (), (override));
MOCK_METHOD(bool, DoThat, (const char* str), (override));
... more mock methods ...
};
更改之后,它看起来像
// File mock_foo.h.
...
class MockFoo : public Foo {
public:
// The constructor and destructor are declared, but not defined, here.
MockFoo();
virtual ~MockFoo();
MOCK_METHOD(int, DoThis, (), (override));
MOCK_METHOD(bool, DoThat, (const char* str), (override));
... more mock methods ...
};
和
// File mock_foo.cc.
#include "path/to/mock_foo.h"
// The definitions may appear trivial, but the functions actually do a
// lot of things through the constructors/destructors of the member
// variables used to implement the mock methods.
MockFoo::MockFoo() {}
MockFoo::~MockFoo() {}
强制验证
当你的 mock 对象被销毁时,它会自动验证对其的所有预期是否已得到满足,如果未满足,则会生成 googletest 失败。这很方便,因为它让你少了一件需要担心的事情。也就是说,除非你不确定你的 mock 对象是否会被销毁。
你的 mock 对象怎么可能最终不会被销毁?好吧,它可能是在堆上创建的,并且由你正在测试的代码拥有。假设该代码中存在一个 bug,并且它没有正确地删除 mock 对象 - 你可能会得到一个通过的测试,但实际上存在一个 bug。
使用堆检查器是一个好主意,可以缓解这种担忧,但它的实现并非 100% 可靠。因此,有时你确实想强制 gMock 在 mock 对象被(希望)销毁之前验证它。你可以使用 Mock::VerifyAndClearExpectations(&mock_object)
来做到这一点
TEST(MyServerTest, ProcessesRequest) {
using ::testing::Mock;
MockFoo* const foo = new MockFoo;
EXPECT_CALL(*foo, ...)...;
// ... other expectations ...
// server now owns foo.
MyServer server(foo);
server.ProcessRequest(...);
// In case that server's destructor will forget to delete foo,
// this will verify the expectations anyway.
Mock::VerifyAndClearExpectations(foo);
} // server is destroyed when it goes out of scope here.
提示: Mock::VerifyAndClearExpectations()
函数返回一个 bool
来指示验证是否成功(true
表示是),因此如果验证失败后没有任何意义继续,你可以将该函数调用包装在 ASSERT_TRUE()
中。
在使用后验证并清除 mock 后,请勿设置新的预期。在执行 mock 的代码之后设置预期会产生未定义的行为。有关更多信息,请参阅 在测试中使用 Mock。
使用检查点
有时你可能希望以每个阶段的大小都可管理的方式测试 mock 对象的行为,或者你可能希望设置有关哪些 API 调用调用哪些 mock 函数的更详细的预期。
你可以使用的一种技术是将预期放入一个序列中,并在特定位置插入对虚拟“检查点”函数的调用。然后,你可以验证 mock 函数调用是否在正确的时间发生。例如,如果你正在执行代码
Foo(1);
Foo(2);
Foo(3);
并且想要验证 Foo(1)
和 Foo(3)
都调用 mock.Bar("a")
,但是 Foo(2)
不调用任何东西,你可以编写
using ::testing::MockFunction;
TEST(FooTest, InvokesBarCorrectly) {
MyMock mock;
// Class MockFunction<F> has exactly one mock method. It is named
// Call() and has type F.
MockFunction<void(string check_point_name)> check;
{
InSequence s;
EXPECT_CALL(mock, Bar("a"));
EXPECT_CALL(check, Call("1"));
EXPECT_CALL(check, Call("2"));
EXPECT_CALL(mock, Bar("a"));
}
Foo(1);
check.Call("1");
Foo(2);
check.Call("2");
Foo(3);
}
预期规范表明,第一个 Bar("a")
调用必须发生在检查点 “1” 之前,第二个 Bar("a")
调用必须发生在检查点 “2” 之后,并且在两个检查点之间不应发生任何事情。显式检查点清楚地表明哪个 Bar("a")
是通过调用 Foo()
来调用的。
Mock 析构函数
有时,你可能需要确保 mock 对象在正确的时间被销毁,例如在调用 bar->A()
之后但在调用 bar->B()
之前。我们已经知道你可以指定 mock 函数调用的顺序约束,所以我们需要做的就是 mock 掉 mock 函数的析构函数。
这听起来很简单,但有一个问题:析构函数是一个特殊的函数,具有特殊的语法和特殊的语义,而 MOCK_METHOD
宏不适用于它。
MOCK_METHOD(void, ~MockFoo, ()); // Won't compile!
好消息是你可以使用一个简单的模式来实现相同的效果。首先,在你的 mock 类中添加一个 mock 函数 Die()
并在析构函数中调用它,如下所示:
class MockFoo : public Foo {
...
// Add the following two lines to the mock class.
MOCK_METHOD(void, Die, ());
~MockFoo() override { Die(); }
};
(如果名称 Die()
与现有符号冲突,请选择另一个名称。)现在,我们将测试 MockFoo
对象何时销毁的问题转化为测试何时调用其 Die()
方法的问题。
MockFoo* foo = new MockFoo;
MockBar* bar = new MockBar;
...
{
InSequence s;
// Expects *foo to die after bar->A() and before bar->B().
EXPECT_CALL(*bar, A());
EXPECT_CALL(*foo, Die());
EXPECT_CALL(*bar, B());
}
就是这样。
使用 gMock 和线程
在一个单元测试中,最好是在单线程上下文中隔离和测试一段代码。这避免了竞争条件和死锁,并使调试你的测试更加容易。
然而,大多数程序都是多线程的,有时为了测试某些东西,我们需要从多个线程对其进行猛烈攻击。gMock 也适用于此目的。
记住使用 mock 的步骤:
- 创建一个 mock 对象
foo
。 - 使用
ON_CALL()
和EXPECT_CALL()
设置其默认行为和期望。 - 被测试的代码调用
foo
的方法。 - 可选地,验证并重置 mock。
- 你自己销毁 mock,或者让被测试的代码销毁它。析构函数将自动验证它。
如果你遵循以下简单规则,你的 mock 和线程可以愉快地一起工作:
- 在一个线程中执行你的测试代码(而不是被测试的代码)。这使你的测试易于理解。
- 显然,你可以在不加锁的情况下执行第 1 步。
- 在执行第 2 步和第 5 步时,确保没有其他线程正在访问
foo
。这也很明显,不是吗? - 第 3 步和第 4 步可以在一个线程或多个线程中完成 - 随便你想要的方式。gMock 会处理锁定,所以你不需要做任何事情 - 除非你的测试逻辑需要。
如果你违反了这些规则(例如,如果在另一个线程调用 mock 的方法时你设置了 mock 的期望),你会得到未定义的行为。这不好玩,所以不要这样做。
gMock 保证 mock 函数的行为在调用该 mock 函数的同一线程中完成。例如,在:
EXPECT_CALL(mock, Foo(1))
.WillOnce(action1);
EXPECT_CALL(mock, Foo(2))
.WillOnce(action2);
如果 Foo(1)
在线程 1 中被调用,而 Foo(2)
在线程 2 中被调用,gMock 将在线程 1 中执行 action1
,并在线程 2 中执行 action2
。
gMock *不*对在不同线程中执行的行为施加顺序(这样做可能会导致死锁,因为这些行为可能需要合作)。这意味着上述示例中 action1
和 action2
的执行*可能*会交错。如果这是一个问题,你应该向 action1
和 action2
添加适当的同步逻辑,以使测试成为线程安全的。
另外,请记住 DefaultValue<T>
是一个全局资源,可能会影响你的程序中*所有*活动的 mock 对象。当然,你不会想从多个线程或当仍然有 mock 在运行时搞乱它。
控制 gMock 打印多少信息
当 gMock 看到一些可能出错的东西时(例如,调用了没有期望的 mock 函数,即所谓的“不感兴趣的调用”,这是允许的,但也许你忘记明确禁止该调用),它会打印一些警告消息,包括函数的参数、返回值和堆栈跟踪。希望这能提醒你查看并确认是否存在问题。
有时你确信你的测试是正确的,可能不太欣赏这些友好的消息。另一些时候,你正在调试你的测试或学习你正在测试的代码的行为,并希望你可以观察到发生的每个 mock 调用(包括参数值、返回值和堆栈跟踪)。显然,一种尺寸并不适合所有人。
你可以使用 --gmock_verbose=LEVEL
命令行标志来控制 gMock 告诉你多少信息,其中 LEVEL
是一个字符串,有三个可能的值:
info
:gMock 将打印所有信息性消息、警告和错误(最详细)。在此设置下,gMock 还会记录对ON_CALL/EXPECT_CALL
宏的任何调用。它将在“不感兴趣的调用”警告中包含堆栈跟踪。warning
:gMock 将打印警告和错误(不太详细);它将省略“不感兴趣的调用”警告中的堆栈跟踪。这是默认设置。error
:gMock 将仅打印错误(最不详细)。
或者,你可以像这样从你的测试中调整该标志的值:
::testing::FLAGS_gmock_verbose = "error";
如果你发现 gMock 在其信息性或警告消息中打印了太多的堆栈帧,请记住你可以使用 --gtest_stack_trace_depth=max_depth
标志来控制它们的数量。
现在,明智地使用正确的标志,让 gMock 更好地为你服务!
获得对 Mock 调用的超级视觉
你有一个使用 gMock 的测试。它失败了:gMock 告诉你一些期望没有得到满足。但是,你不确定为什么:匹配器中的某个地方是否有错字?你是否搞砸了 EXPECT_CALL
的顺序?还是被测试的代码做错了什么?你如何找出原因?
如果你拥有 X 光视觉,并且可以实际看到所有 EXPECT_CALL
和 mock 方法调用的踪迹,那不是很好吗?对于每个调用,你想看到它的实际参数值以及 gMock 认为它匹配哪个 EXPECT_CALL
吗?如果你仍然需要一些帮助来弄清楚是谁进行了这些调用,那么能够看到每个 mock 调用处的完整堆栈跟踪怎么样?
你可以通过使用 --gmock_verbose=info
标志运行你的测试来解锁此能力。例如,给定测试程序:
#include <gmock/gmock.h>
using ::testing::_;
using ::testing::HasSubstr;
using ::testing::Return;
class MockFoo {
public:
MOCK_METHOD(void, F, (const string& x, const string& y));
};
TEST(Foo, Bar) {
MockFoo mock;
EXPECT_CALL(mock, F(_, _)).WillRepeatedly(Return());
EXPECT_CALL(mock, F("a", "b"));
EXPECT_CALL(mock, F("c", HasSubstr("d")));
mock.F("a", "good");
mock.F("a", "b");
}
如果你使用 --gmock_verbose=info
运行它,你将看到以下输出:
[ RUN ] Foo.Bar
foo_test.cc:14: EXPECT_CALL(mock, F(_, _)) invoked
Stack trace: ...
foo_test.cc:15: EXPECT_CALL(mock, F("a", "b")) invoked
Stack trace: ...
foo_test.cc:16: EXPECT_CALL(mock, F("c", HasSubstr("d"))) invoked
Stack trace: ...
foo_test.cc:14: Mock function call matches EXPECT_CALL(mock, F(_, _))...
Function call: F(@0x7fff7c8dad40"a",@0x7fff7c8dad10"good")
Stack trace: ...
foo_test.cc:15: Mock function call matches EXPECT_CALL(mock, F("a", "b"))...
Function call: F(@0x7fff7c8dada0"a",@0x7fff7c8dad70"b")
Stack trace: ...
foo_test.cc:16: Failure
Actual function call count doesn't match EXPECT_CALL(mock, F("c", HasSubstr("d")))...
Expected: to be called once
Actual: never called - unsatisfied and active
[ FAILED ] Foo.Bar
假设 bug 是第三个 EXPECT_CALL
中的 "c"
是一个错字,实际上应该是 "a"
。通过以上消息,你应该看到实际的 F("a", "good")
调用由第一个 EXPECT_CALL
匹配,而不是你认为的第三个。由此应该可以明显看出第三个 EXPECT_CALL
写错了。问题解决。
如果你对 mock 调用跟踪感兴趣,但对堆栈跟踪不感兴趣,你可以在测试命令行上将 --gmock_verbose=info
与 --gtest_stack_trace_depth=0
结合使用。
在 Emacs 中运行测试
如果你使用 M-x google-compile
命令在 Emacs 中构建和运行你的测试(就像许多 googletest 用户那样),gMock 和 googletest 错误的源文件位置将被突出显示。只需在其中一个错误上按 <Enter>
,你就会被带到出错的行。或者,你可以只输入 C-x
` 来跳转到下一个错误。
为了使其更容易,你可以将以下行添加到你的 ~/.emacs
文件中:
(global-set-key "\M-m" 'google-compile) ; m is for make
(global-set-key [M-down] 'next-error)
(global-set-key [M-up] '(lambda () (interactive) (next-error -1)))
然后你可以输入 M-m
来开始构建(如果你也想运行测试,只需确保 foo_test.run
或 runtests
在你输入 M-m
后提供的构建命令中),或者 M-up
/M-down
来在错误之间前后移动。
扩展 gMock
快速编写新的 Matcher
警告:gMock 不保证 matcher 将被调用多少次或何时调用。因此,所有 matcher 必须是纯函数。有关更多详细信息,请参见此部分。
MATCHER*
宏系列可用于轻松定义自定义 matcher。语法:
MATCHER(name, description_string_expression) { statements; }
将定义一个具有给定名称的 matcher,该 matcher 执行 statements,statements 必须返回一个 bool
,以指示匹配是否成功。在 statements 内部,你可以使用 arg
引用正在匹配的值,并使用 arg_type
引用其类型。
description string 是一个 string
类型的表达式,用于记录 matcher 的作用,并用于在匹配失败时生成失败消息。它可以(并且应该)引用特殊的 bool
变量 negation
,并且应该在 negation
为 false
时计算为 matcher 的描述,或者在 negation
为 true
时计算为 matcher 的否定的描述。
为了方便起见,我们允许 description string 为空(""
),在这种情况下,gMock 将使用 matcher 名称中的单词序列作为描述。
基本示例
MATCHER(IsDivisibleBy7, "") { return (arg % 7) == 0; }
允许你编写:
// Expects mock_foo.Bar(n) to be called where n is divisible by 7.
EXPECT_CALL(mock_foo, Bar(IsDivisibleBy7()));
或:
using ::testing::Not;
...
// Verifies that a value is divisible by 7 and the other is not.
EXPECT_THAT(some_expression, IsDivisibleBy7());
EXPECT_THAT(some_other_expression, Not(IsDivisibleBy7()));
如果以上断言失败,它们将打印类似以下内容:
Value of: some_expression
Expected: is divisible by 7
Actual: 27
...
Value of: some_other_expression
Expected: not (is divisible by 7)
Actual: 21
其中描述 "is divisible by 7"
和 "not (is divisible by 7)"
是从 matcher 名称 IsDivisibleBy7
自动计算得出的。
添加自定义失败消息
你可能已经注意到,自动生成的描述(尤其是否定的描述)可能不太好。你可以始终使用你自己的 string
表达式覆盖它们:
MATCHER(IsDivisibleBy7,
absl::StrCat(negation ? "isn't" : "is", " divisible by 7")) {
return (arg % 7) == 0;
}
可选地,你可以将额外的信息流式传输到名为 result_listener
的隐藏参数,以解释匹配结果。例如,IsDivisibleBy7
的更好定义是:
MATCHER(IsDivisibleBy7, "") {
if ((arg % 7) == 0)
return true;
*result_listener << "the remainder is " << (arg % 7);
return false;
}
使用此定义,以上断言将提供更好的消息:
Value of: some_expression
Expected: is divisible by 7
Actual: 27 (the remainder is 6)
在 Matcher 中使用 EXPECT_ 语句
你也可以在自定义 matcher 定义中使用 EXPECT_...
语句。在许多情况下,这使你可以更简洁地编写你的 matcher,同时仍然提供信息丰富的错误消息。例如:
MATCHER(IsDivisibleBy7, "") {
const auto remainder = arg % 7;
EXPECT_EQ(remainder, 0);
return true;
}
如果你编写一个包含行 EXPECT_THAT(27, IsDivisibleBy7());
的测试,你将得到类似以下的错误:
Expected equality of these values:
remainder
Which is: 6
0
MatchAndExplain
你应该让 MatchAndExplain()
打印任何额外的信息,以便帮助用户理解匹配结果。请注意,如果匹配成功,它应该解释为什么匹配成功(除非显而易见)- 这在匹配器在 Not()
内部使用时非常有用。 没有必要打印参数值本身,因为 gMock 已经为你打印了。
参数类型
被匹配的值的类型 (arg_type
) 由你使用匹配器的上下文决定,并由编译器提供给你,所以你不需要担心声明它(也不能声明)。这使得匹配器具有多态性。例如,IsDivisibleBy7()
可用于匹配任何类型,其中 (arg % 7) == 0
的值可以隐式转换为 bool
。在上面的 Bar(IsDivisibleBy7())
示例中,如果方法 Bar()
接受一个 int
,则 arg_type
将是 int
;如果它接受一个 unsigned long
,则 arg_type
将是 unsigned long
;等等。
快速编写新的参数化匹配器
有时你想要定义一个带有参数的匹配器。为此,你可以使用宏
MATCHER_P(name, param_name, description_string) { statements; }
其中描述字符串可以是 ""
,也可以是引用 negation
和 param_name
的 string
表达式。
例如:
MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; }
将允许你编写
EXPECT_THAT(Blah("a"), HasAbsoluteValue(n));
这可能会导致此消息(假设 n
是 10)
Value of: Blah("a")
Expected: has absolute value 10
Actual: -9
请注意,匹配器描述及其参数都被打印出来,使得消息对人类友好。
在匹配器定义体中,你可以编写 foo_type
来引用名为 foo
的参数的类型。例如,在上面的 MATCHER_P(HasAbsoluteValue, value)
的主体中,你可以编写 value_type
来引用 value
的类型。
gMock 还提供了 MATCHER_P2
、MATCHER_P3
、…、直到 MATCHER_P10
来支持多参数匹配器
MATCHER_Pk(name, param_1, ..., param_k, description_string) { statements; }
请注意,自定义描述字符串适用于匹配器的特定实例,其中参数已绑定到实际值。因此,通常你会希望参数值成为描述的一部分。gMock 允许你通过在描述字符串表达式中引用匹配器参数来实现这一点。
例如,
using ::testing::PrintToString;
MATCHER_P2(InClosedRange, low, hi,
absl::StrFormat("%s in range [%s, %s]", negation ? "isn't" : "is",
PrintToString(low), PrintToString(hi))) {
return low <= arg && arg <= hi;
}
...
EXPECT_THAT(3, InClosedRange(4, 6));
将生成一个包含消息的失败
Expected: is in range [4, 6]
如果你指定 ""
作为描述,则失败消息将包含匹配器名称中的单词序列,后跟作为元组打印的参数值。例如,
MATCHER_P2(InClosedRange, low, hi, "") { ... }
...
EXPECT_THAT(3, InClosedRange(4, 6));
将生成包含文本的失败
Expected: in closed range (4, 6)
出于类型目的,你可以将
MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... }
视为简写形式
template <typename p1_type, ..., typename pk_type>
FooMatcherPk<p1_type, ..., pk_type>
Foo(p1_type p1, ..., pk_type pk) { ... }
当你编写 Foo(v1, ..., vk)
时,编译器会为你推断参数 v1
, …, 和 vk
的类型。如果你对类型推断的结果不满意,你可以通过显式实例化模板来指定类型,如 Foo<long, bool>(5, false)
。正如前面所说,你无法(或不需要)指定 arg_type
,因为这由使用匹配器的上下文决定。
你可以将表达式 Foo(p1, ..., pk)
的结果分配给 FooMatcherPk<p1_type, ..., pk_type>
类型的变量。这在组合匹配器时很有用。没有参数或只有一个参数的匹配器具有特殊的类型:你可以将 Foo()
分配给 FooMatcher
类型的变量,并将 Foo(p)
分配给 FooMatcherP<p_type>
类型的变量。
虽然你可以使用引用类型实例化匹配器模板,但通过指针传递参数通常使你的代码更具可读性。但是,如果你仍然想通过引用传递参数,请注意,在匹配器生成的失败消息中,你将看到被引用对象的值,但看不到其地址。
你可以重载具有不同数量参数的匹配器
MATCHER_P(Blah, a, description_string_1) { ... }
MATCHER_P2(Blah, a, b, description_string_2) { ... }
虽然在定义新的匹配器时总是使用 MATCHER*
宏很诱人,但你也应该考虑直接实现匹配器接口(参见下面的配方),特别是如果你需要经常使用匹配器的话。虽然这些方法需要更多的工作,但它们让你对被匹配的值和匹配器参数的类型有更多的控制,这通常会导致更好的编译器错误消息,从长远来看是有回报的。它们还允许基于参数类型(而不仅仅是基于参数的数量)来重载匹配器。
编写新的单态匹配器
类型为 testing::Matcher<T>
的匹配器实现了 T
的匹配器接口,并执行两项操作:它测试类型为 T
的值是否与匹配器匹配,并且可以描述它匹配的值的类型。后一种能力用于在违反期望时生成可读的错误消息。一些匹配器甚至可以解释它为什么匹配或不匹配某个值,这在原因不明显时很有帮助。
因为特定类型 T
的类型为 testing::Matcher<T>
的匹配器只能用于匹配类型为 T
的值,所以我们称它为“单态”。
T
的匹配器必须声明一个 typedef,例如
using is_gtest_matcher = void;
并支持以下操作
// Match a value and optionally explain into an ostream.
bool matched = matcher.MatchAndExplain(value, maybe_os);
// where `value` is of type `T` and
// `maybe_os` is of type `std::ostream*`, where it can be null if the caller
// is not interested in there textual explanation.
matcher.DescribeTo(os);
matcher.DescribeNegationTo(os);
// where `os` is of type `std::ostream*`.
如果你需要一个自定义匹配器,但 Truly()
不是一个好的选择(例如,你可能对 Truly(predicate)
描述自身的方式不满意,或者你可能希望你的匹配器像 Eq(value)
一样是多态的),你可以定义一个匹配器来执行你想要的任何操作,分两个步骤:首先实现匹配器接口,然后定义一个工厂函数来创建匹配器实例。第二步不是严格需要的,但它使使用匹配器的语法更加简洁。
例如,你可以定义一个匹配器来测试一个 int
是否可以被 7 整除,然后像这样使用它
using ::testing::Matcher;
class DivisibleBy7Matcher {
public:
using is_gtest_matcher = void;
bool MatchAndExplain(int n, std::ostream*) const {
return (n % 7) == 0;
}
void DescribeTo(std::ostream* os) const {
*os << "is divisible by 7";
}
void DescribeNegationTo(std::ostream* os) const {
*os << "is not divisible by 7";
}
};
Matcher<int> DivisibleBy7() {
return DivisibleBy7Matcher();
}
...
EXPECT_CALL(foo, Bar(DivisibleBy7()));
你可以通过将额外的信息流式传输到 MatchAndExplain()
中的 os
参数来改进匹配器消息
class DivisibleBy7Matcher {
public:
bool MatchAndExplain(int n, std::ostream* os) const {
const int remainder = n % 7;
if (remainder != 0 && os != nullptr) {
*os << "the remainder is " << remainder;
}
return remainder == 0;
}
...
};
然后,EXPECT_THAT(x, DivisibleBy7());
可能会生成这样的消息
Value of: x
Expected: is divisible by 7
Actual: 23 (the remainder is 2)
提示:为方便起见,MatchAndExplain()
可以接受 MatchResultListener*
而不是 std::ostream*
。
编写新的多态匹配器
与只能用于匹配特定类型的值的单态匹配器不同,多态匹配器是可用于匹配多种类型的值的匹配器。例如,Eq(5)
是一个多态匹配器,因为它可以用于匹配 int
、double
、float
等。你应该将多态匹配器视为一个匹配器工厂,而不是 testing::Matcher<SomeType>
- 它本身不是一个实际的匹配器,但可以隐式转换为 testing::Matcher<SomeType>
,具体取决于上下文。
现在,将我们上面学到的知识扩展到多态匹配器,就像在正确的位置添加模板一样简单。
class NotNullMatcher {
public:
using is_gtest_matcher = void;
// To implement a polymorphic matcher, we just need to make MatchAndExplain a
// template on its first argument.
// In this example, we want to use NotNull() with any pointer, so
// MatchAndExplain() accepts a pointer of any type as its first argument.
// In general, you can define MatchAndExplain() as an ordinary method or
// a method template, or even overload it.
template <typename T>
bool MatchAndExplain(T* p, std::ostream*) const {
return p != nullptr;
}
// Describes the property of a value matching this matcher.
void DescribeTo(std::ostream* os) const { *os << "is not NULL"; }
// Describes the property of a value NOT matching this matcher.
void DescribeNegationTo(std::ostream* os) const { *os << "is NULL"; }
};
NotNullMatcher NotNull() {
return NotNullMatcher();
}
...
EXPECT_CALL(foo, Bar(NotNull())); // The argument must be a non-NULL pointer.
旧版匹配器实现
定义匹配器曾经有点复杂,它需要几个支持类和虚函数。要使用旧版 API 为类型 T
实现匹配器,你必须从 MatcherInterface<T>
派生并调用 MakeMatcher
来构造对象。
接口如下所示
class MatchResultListener {
public:
...
// Streams x to the underlying ostream; does nothing if the ostream
// is NULL.
template <typename T>
MatchResultListener& operator<<(const T& x);
// Returns the underlying ostream.
std::ostream* stream();
};
template <typename T>
class MatcherInterface {
public:
virtual ~MatcherInterface();
// Returns true if and only if the matcher matches x; also explains the match
// result to 'listener'.
virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0;
// Describes this matcher to an ostream.
virtual void DescribeTo(std::ostream* os) const = 0;
// Describes the negation of this matcher to an ostream.
virtual void DescribeNegationTo(std::ostream* os) const;
};
幸运的是,大多数时候,你可以借助 MakePolymorphicMatcher()
轻松定义多态匹配器。以下是如何将 NotNull()
定义为示例
using ::testing::MakePolymorphicMatcher;
using ::testing::MatchResultListener;
using ::testing::PolymorphicMatcher;
class NotNullMatcher {
public:
// To implement a polymorphic matcher, first define a COPYABLE class
// that has three members MatchAndExplain(), DescribeTo(), and
// DescribeNegationTo(), like the following.
// In this example, we want to use NotNull() with any pointer, so
// MatchAndExplain() accepts a pointer of any type as its first argument.
// In general, you can define MatchAndExplain() as an ordinary method or
// a method template, or even overload it.
template <typename T>
bool MatchAndExplain(T* p,
MatchResultListener* /* listener */) const {
return p != NULL;
}
// Describes the property of a value matching this matcher.
void DescribeTo(std::ostream* os) const { *os << "is not NULL"; }
// Describes the property of a value NOT matching this matcher.
void DescribeNegationTo(std::ostream* os) const { *os << "is NULL"; }
};
// To construct a polymorphic matcher, pass an instance of the class
// to MakePolymorphicMatcher(). Note the return type.
PolymorphicMatcher<NotNullMatcher> NotNull() {
return MakePolymorphicMatcher(NotNullMatcher());
}
...
EXPECT_CALL(foo, Bar(NotNull())); // The argument must be a non-NULL pointer.
注意:你的多态匹配器类不需要从 MatcherInterface
或任何其他类继承,并且其方法不需要是虚拟的。
与单态匹配器一样,你可以通过将额外的信息流式传输到 MatchAndExplain()
中的 listener
参数来解释匹配结果。
实现复合匹配器
有时我们想要定义一个将其他匹配器作为参数的匹配器。例如,DistanceFrom(target, m)
是一个多态匹配器,它接受一个匹配器 m
作为参数。它测试从 target
到被匹配值的距离是否满足子匹配器 m
。
如果你正在实现这样的复合匹配器,你将需要根据其子匹配器的描述生成匹配器的描述。你可以在 googlemock/include/gmock/gmock-matchers.h
中查看 DistanceFrom()
的实现作为示例。特别是,请注意 DistanceFromMatcherImpl
。请注意,它将子匹配器存储为 const Matcher<const Distance&> distance_matcher_
而不是多态匹配器 - 这允许它调用 distance_matcher_.DescribeTo(os)
来描述子匹配器。如果子匹配器存储为多态匹配器,则无法获得其描述,因为通常多态匹配器不知道如何描述自身 - 它们是匹配器工厂而不是实际的匹配器;只有在转换为 Matcher<SomeType>
后才能描述它们。
编写新的基数
基数用于 Times()
中,以告诉 gMock 你期望调用发生的次数。它不必是精确的。例如,你可以说 AtLeast(5)
或 Between(2, 4)
。
如果 内置的基数集合不适合你,你可以通过实现以下接口(在命名空间 testing
中)来自由定义你自己的基数
class CardinalityInterface {
public:
virtual ~CardinalityInterface();
// Returns true if and only if call_count calls will satisfy this cardinality.
virtual bool IsSatisfiedByCallCount(int call_count) const = 0;
// Returns true if and only if call_count calls will saturate this
// cardinality.
virtual bool IsSaturatedByCallCount(int call_count) const = 0;
// Describes self to an ostream.
virtual void DescribeTo(std::ostream* os) const = 0;
};
例如,要指定调用必须发生偶数次,你可以编写
using ::testing::Cardinality;
using ::testing::CardinalityInterface;
using ::testing::MakeCardinality;
class EvenNumberCardinality : public CardinalityInterface {
public:
bool IsSatisfiedByCallCount(int call_count) const override {
return (call_count % 2) == 0;
}
bool IsSaturatedByCallCount(int call_count) const override {
return false;
}
void DescribeTo(std::ostream* os) const {
*os << "called even number of times";
}
};
Cardinality EvenNumber() {
return MakeCardinality(new EvenNumberCardinality);
}
...
EXPECT_CALL(foo, Bar(3))
.Times(EvenNumber());
编写新的 Action
如果内置的 action 不满足你的需求,你可以很容易地定义自己的 action。你只需要一个具有与模拟函数兼容的签名的调用运算符。因此,你可以使用 lambda 表达式。
MockFunction<int(int)> mock;
EXPECT_CALL(mock, Call).WillOnce([](const int input) { return input * 7; });
EXPECT_EQ(mock.AsStdFunction()(2), 14);
或者一个带有调用运算符的结构体(甚至是模板化的)。
struct MultiplyBy {
template <typename T>
T operator()(T arg) { return arg * multiplier; }
int multiplier;
};
// Then use:
// EXPECT_CALL(...).WillOnce(MultiplyBy{7});
调用对象不接受任何参数,忽略传递给模拟函数的参数也是可以的。
MockFunction<int(int)> mock;
EXPECT_CALL(mock, Call).WillOnce([] { return 17; });
EXPECT_EQ(mock.AsStdFunction()(0), 17);
与 WillOnce
一起使用时,可调用对象可以假设它最多被调用一次,并且允许是 move-only 类型。
// An action that contains move-only types and has an &&-qualified operator,
// demanding in the type system that it be called at most once. This can be
// used with WillOnce, but the compiler will reject it if handed to
// WillRepeatedly.
struct MoveOnlyAction {
std::unique_ptr<int> move_only_state;
std::unique_ptr<int> operator()() && { return std::move(move_only_state); }
};
MockFunction<std::unique_ptr<int>()> mock;
EXPECT_CALL(mock, Call).WillOnce(MoveOnlyAction{std::make_unique<int>(17)});
EXPECT_THAT(mock.AsStdFunction()(), Pointee(Eq(17)));
更一般地说,要与签名是 R(Args...)
的模拟函数一起使用,该对象可以是任何可以转换为 OnceAction<R(Args...)>
或 Action<R(Args...)>
的对象。 两者之间的区别在于 OnceAction
具有更弱的要求(Action
需要一个可复制构造的输入,可以重复调用,而 OnceAction
只需要可移动构造,并支持 &&
限定的调用运算符),但只能与 WillOnce
一起使用。OnceAction
通常仅在支持 move-only 类型或希望类型系统保证它们最多被调用一次的 action 时才相关。
通常,OnceAction
和 Action
模板无需在你的 action 中直接引用:具有调用运算符的结构体或类就足够了,如上面的示例所示。 但是,需要知道模拟函数的特定返回类型的更复杂的 polymorphism action 可以定义模板化的转换运算符来实现这一点。 有关示例,请参见 gmock-actions.h
。
基于宏的旧式 Actions
在 C++11 之前,不支持基于 functor 的 action;编写 action 的旧方法是通过一组 ACTION*
宏。 我们建议在新代码中避免使用它们;它们将大量逻辑隐藏在宏之后,可能导致难以理解的编译器错误。 尽管如此,为了完整起见,我们在此处介绍它们。
通过编写
ACTION(name) { statements; }
在命名空间范围内(即,不在类或函数内部),你将定义一个具有给定名称的 action,该 action 执行语句。 statements
返回的值将用作 action 的返回值。 在语句内部,你可以将模拟函数的第 K 个(从 0 开始)参数称为 argK
。 例如
ACTION(IncrementArg1) { return ++(*arg1); }
允许你编写:
... WillOnce(IncrementArg1());
请注意,你无需指定模拟函数参数的类型。 请放心,你的代码是类型安全的:如果 *arg1
不支持 ++
运算符,或者 ++(*arg1)
的类型与模拟函数的返回类型不兼容,你将收到编译器错误。
另一个例子
ACTION(Foo) {
(*arg2)(5);
Blah();
*arg1 = 0;
return arg0;
}
定义了一个 action Foo()
,它使用 5 调用参数 #2(函数指针),调用函数 Blah()
,将参数 #1 指向的值设置为 0,并返回参数 #0。
为了更方便和灵活,你还可以使用 ACTION
主体中的以下预定义符号
argK_type |
模拟函数的第 K 个(从 0 开始)参数的类型 |
---|---|
args |
模拟函数的所有参数作为元组 |
args_type |
模拟函数的所有参数的类型作为元组 |
return_type |
模拟函数的返回类型 |
function_type |
模拟函数的类型 |
例如,当使用 ACTION
作为模拟函数的存根 action 时
int DoSomething(bool flag, int* ptr);
我们有
预定义符号 | 绑定到 |
---|---|
arg0 |
flag 的值 |
arg0_type |
类型 bool |
arg1 |
ptr 的值 |
arg1_type |
类型 int* |
args |
元组 (flag, ptr) |
args_type |
类型 std::tuple<bool, int*> |
return_type |
类型 int |
function_type |
类型 int(bool, int*) |
基于宏的旧式参数化 Actions
有时你想要参数化你定义的 action。为此,我们有另一个宏
ACTION_P(name, param) { statements; }
例如,
ACTION_P(Add, n) { return arg0 + n; }
将允许你编写
// Returns argument #0 + 5.
... WillOnce(Add(5));
为方便起见,我们使用术语 *arguments* 表示用于调用模拟函数的值,使用术语 *parameters* 表示用于实例化 action 的值。
请注意,你也不需要提供参数的类型。假设参数名为 param
,你还可以使用 gMock 定义的符号 param_type
来引用编译器推断的参数类型。例如,在上面的 ACTION_P(Add, n)
的主体中,你可以为 n
的类型编写 n_type
。
gMock 还提供了 ACTION_P2
, ACTION_P3
等来支持多参数 action。例如,
ACTION_P2(ReturnDistanceTo, x, y) {
double dx = arg0 - x;
double dy = arg1 - y;
return sqrt(dx*dx + dy*dy);
}
让你编写
... WillOnce(ReturnDistanceTo(5.0, 26.5));
你可以将 ACTION
视为参数数量为 0 的退化的参数化 action。
你还可以轻松地定义根据参数数量重载的 action。
ACTION_P(Plus, a) { ... }
ACTION_P2(Plus, a, b) { ... }
限制 ACTION 中参数或参数的类型
为了最大限度地简洁和可重用性,ACTION*
宏不会要求你提供模拟函数参数和 action 参数的类型。相反,我们让编译器为我们推断类型。
但是,有时我们可能希望对类型更加明确。有几个技巧可以做到这一点。例如
ACTION(Foo) {
// Makes sure arg0 can be converted to int.
int n = arg0;
... use n instead of arg0 here ...
}
ACTION_P(Bar, param) {
// Makes sure the type of arg1 is const char*.
::testing::StaticAssertTypeEq<const char*, arg1_type>();
// Makes sure param can be converted to bool.
bool flag = param;
}
其中 StaticAssertTypeEq
是 googletest 中的编译时断言,用于验证两种类型是否相同。
快速编写新的 Action 模板
有时你希望给 action 显式模板参数,这些参数无法从其值参数推断出来。 ACTION_TEMPLATE()
支持这一点,可以看作是 ACTION()
和 ACTION_P*()
的扩展。
语法
ACTION_TEMPLATE(ActionName,
HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m),
AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; }
定义一个 action 模板,该模板采用 *m* 个显式模板参数和 *n* 个值参数,其中 *m* 在 [1, 10] 中,*n* 在 [0, 10] 中。 name_i
是第 *i* 个模板参数的名称,kind_i
指定它是 typename
、整数常量还是模板。 p_i
是第 *i* 个值参数的名称。
例子
// DuplicateArg<k, T>(output) converts the k-th argument of the mock
// function to type T and copies it to *output.
ACTION_TEMPLATE(DuplicateArg,
// Note the comma between int and k:
HAS_2_TEMPLATE_PARAMS(int, k, typename, T),
AND_1_VALUE_PARAMS(output)) {
*output = T(std::get<k>(args));
}
要创建 action 模板的实例,请编写
ActionName<t1, ..., t_m>(v1, ..., v_n)
其中 t
是模板参数,v
是值参数。 值参数类型由编译器推断。 例如
using ::testing::_;
...
int n;
EXPECT_CALL(mock, Foo).WillOnce(DuplicateArg<1, unsigned char>(&n));
如果你想显式指定值参数类型,你可以提供额外的模板参数
ActionName<t1, ..., t_m, u1, ..., u_k>(v1, ..., v_n)
其中 u_i
是 v_i
的所需类型。
ACTION_TEMPLATE
和 ACTION
/ACTION_P*
可以在值参数的数量上重载,但不能在模板参数的数量上重载。 如果没有限制,以下含义不明确
OverloadedAction<int, bool>(x);
我们是使用单个模板参数 action,其中 bool
指的是 x
的类型,还是使用两个模板参数 action,其中编译器被要求推断 x
的类型?
使用 ACTION 对象的类型
如果你正在编写一个返回 ACTION
对象的函数,你需要知道它的类型。 该类型取决于用于定义 action 的宏和参数类型。 规则相对简单
给定定义 | 表达式 | 具有类型 |
---|---|---|
ACTION(Foo) |
Foo() |
FooAction |
ACTION_TEMPLATE(Foo, HAS_m_TEMPLATE_PARAMS(...), AND_0_VALUE_PARAMS()) |
Foo<t1, ..., t_m>() |
FooAction<t1, ..., t_m> |
ACTION_P(Bar, param) |
Bar(int_value) |
BarActionP<int> |
ACTION_TEMPLATE(Bar, HAS_m_TEMPLATE_PARAMS(...), AND_1_VALUE_PARAMS(p1)) |
Bar<t1, ..., t_m>(int_value) |
BarActionP<t1, ..., t_m, int> |
ACTION_P2(Baz, p1, p2) |
Baz(bool_value, int_value) |
BazActionP2<bool, int> |
ACTION_TEMPLATE(Baz, HAS_m_TEMPLATE_PARAMS(...), AND_2_VALUE_PARAMS(p1, p2)) |
Baz<t1, ..., t_m>(bool_value, int_value) |
BazActionP2<t1, ..., t_m, bool, int> |
… | … | … |
请注意,我们必须为具有不同数量值参数的 action 选择不同的后缀(Action
、ActionP
、ActionP2
等),否则 action 定义无法在它们的数量上重载。
编写新的 Monomorphic Actions
虽然 ACTION*
宏非常方便,但有时它们并不合适。例如,尽管之前的说明中显示了一些技巧,但它们不允许你直接指定模拟函数参数和 action 参数的类型,这通常会导致未优化的编译器错误消息,这些消息可能会使不熟悉的用户感到困惑。它们也不允许在没有经过一些步骤的情况下,基于参数类型重载 action。
除了 ACTION*
宏之外,另一种选择是实现 ::testing::ActionInterface<F>
,其中 F
是将在其中使用 action 的模拟函数的类型。 例如
template <typename F>
class ActionInterface {
public:
virtual ~ActionInterface();
// Performs the action. Result is the return type of function type
// F, and ArgumentTuple is the tuple of arguments of F.
//
// For example, if F is int(bool, const string&), then Result would
// be int, and ArgumentTuple would be std::tuple<bool, const string&>.
virtual Result Perform(const ArgumentTuple& args) = 0;
};
using ::testing::_;
using ::testing::Action;
using ::testing::ActionInterface;
using ::testing::MakeAction;
typedef int IncrementMethod(int*);
class IncrementArgumentAction : public ActionInterface<IncrementMethod> {
public:
int Perform(const std::tuple<int*>& args) override {
int* p = std::get<0>(args); // Grabs the first argument.
return *p++;
}
};
Action<IncrementMethod> IncrementArgument() {
return MakeAction(new IncrementArgumentAction);
}
...
EXPECT_CALL(foo, Baz(_))
.WillOnce(IncrementArgument());
int n = 5;
foo.Baz(&n); // Should return 5 and change n to 6.
编写新的 Polymorphic Actions
之前的示例展示了如何定义你自己的 action。这很好,但你需要知道使用 action 的函数类型。有时这会成为问题。例如,如果你想在具有不同类型的函数中使用 action (例如像 Return()
和 SetArgPointee()
)。
如果一个 action 可以用于多种类型的 mock 函数,我们称之为多态。MakePolymorphicAction()
函数模板可以轻松定义这样的 action。
namespace testing {
template <typename Impl>
PolymorphicAction<Impl> MakePolymorphicAction(const Impl& impl);
} // namespace testing
举个例子,让我们定义一个 action,它返回 mock 函数参数列表中的第二个参数。第一步是定义一个实现类。
class ReturnSecondArgumentAction {
public:
template <typename Result, typename ArgumentTuple>
Result Perform(const ArgumentTuple& args) const {
// To get the i-th (0-based) argument, use std::get(args).
return std::get<1>(args);
}
};
这个实现类不需要继承自任何特定的类。重要的是它必须有一个 Perform()
方法模板。这个方法模板接受 mock 函数的参数作为一个元组,以单个参数的形式传入,并返回 action 的结果。它可以是 const
或不是,但必须可以通过恰好一个模板参数调用,该参数是结果类型。换句话说,你必须能够调用 Perform<R>(args)
,其中 R
是 mock 函数的返回类型,而 args
是其作为元组的参数。
接下来,我们使用 MakePolymorphicAction()
将实现类的实例转换为我们需要的多态 action。拥有一个包装器会很方便。
using ::testing::MakePolymorphicAction;
using ::testing::PolymorphicAction;
PolymorphicAction<ReturnSecondArgumentAction> ReturnSecondArgument() {
return MakePolymorphicAction(ReturnSecondArgumentAction());
}
现在,你可以像使用内置 action 一样使用这个多态 action。
using ::testing::_;
class MockFoo : public Foo {
public:
MOCK_METHOD(int, DoThis, (bool flag, int n), (override));
MOCK_METHOD(string, DoThat, (int x, const char* str1, const char* str2),
(override));
};
...
MockFoo foo;
EXPECT_CALL(foo, DoThis).WillOnce(ReturnSecondArgument());
EXPECT_CALL(foo, DoThat).WillOnce(ReturnSecondArgument());
...
foo.DoThis(true, 5); // Will return 5.
foo.DoThat(1, "Hi", "Bye"); // Will return "Hi".
教 gMock 如何打印你的值
当发生一个不重要的或意外的调用时,gMock 会打印参数值和堆栈跟踪来帮助你调试。断言宏,例如 EXPECT_THAT
和 EXPECT_EQ
,在断言失败时也会打印相关的值。gMock 和 googletest 使用 googletest 的用户可扩展的值打印机来完成此操作。
这个打印机知道如何打印内置的 C++ 类型、原生数组、STL 容器以及任何支持 <<
运算符的类型。对于其他类型,它会打印值中的原始字节,并希望用户能够理解。GoogleTest 高级指南 解释了如何扩展打印机,使其在打印特定类型时比转储字节做得更好。
使用 gMock 创建的有用 Mock
Mock std::function
std::function
是 C++11 中引入的一种通用函数类型。 它是将回调传递给新接口的首选方式。 函数是可复制的,并且通常不是通过指针传递,这使得模拟它们变得棘手。 但不要害怕 - MockFunction
可以帮助你。
MockFunction<R(T1, ..., Tn)>
有一个 mock 方法 Call()
,其签名为
R Call(T1, ..., Tn);
它还有一个 AsStdFunction()
方法,它创建一个 std::function
代理,转发到 Call
std::function<R(T1, ..., Tn)> AsStdFunction();
要使用 MockFunction
,首先创建 MockFunction
对象,并在其 Call
方法上设置期望。然后将从 AsStdFunction()
获取的代理传递给你正在测试的代码。例如
TEST(FooTest, RunsCallbackWithBarArgument) {
// 1. Create a mock object.
MockFunction<int(string)> mock_function;
// 2. Set expectations on Call() method.
EXPECT_CALL(mock_function, Call("bar")).WillOnce(Return(1));
// 3. Exercise code that uses std::function.
Foo(mock_function.AsStdFunction());
// Foo's signature can be either of:
// void Foo(const std::function<int(string)>& fun);
// void Foo(std::function<int(string)> fun);
// 4. All expectations will be verified when mock_function
// goes out of scope and is destroyed.
}
请记住,使用 AsStdFunction()
创建的函数对象只是转发器。 如果你创建多个,它们将共享同一组期望。
尽管 std::function
支持无限数量的参数,但 MockFunction
的实现限制为十个。 如果你达到了这个限制…… 那么,你的回调除了可以被 mock 之外,还有更大的问题。:-)