kmock 教程
您可以在此处找到使用 kMock 的教程。如果您还没有阅读过,请阅读 入门教程 首先确保您了解 基础知识。
注意: kMock 存在于 testing 命名空间中。为了可读性,它是
建议在使用之前在文件中写入一次using ::testing::Foo;
名称 Foo 由 kMock 定义。我们在本节中省略此类“using”语句
简洁,但您应该在自己的代码中执行此操作。
创建模拟类
模拟类被定义为普通类,使用MOCK_METHOD宏来
生成模拟方法。该宏有 3 或 4 个参数:
class MyMock {
public:
MOCK_METHOD(ReturnType, MethodName, (Args...));
MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...));
};
前 3 个参数只是方法声明,分为 3 部分。 第四个参数接受限定符的封闭列表,这会影响 生成方法:
const- 使模拟方法成为“const”方法。必需的,如果 重写const方法。override- 用“override”标记方法。如果覆盖则推荐 一个“虚拟”方法。noexcept- 用“noexcept”标记方法。如果覆盖则需要noexcept方法。Calltype(...)- 设置方法的调用类型(例如STDMETHODCALLTYPE),在 Windows 中很有用。ref(...)- 使用参考限定标记方法 指定的。如果重写具有引用的方法,则为必需 资格。例如“ref(&)”或“ref(&&)”。
处理不受保护的逗号
不受保护的逗号,即没有被括号包围的逗号,可以防止
正确解析其参数的MOCK_METHOD:
{: .bad}
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 - 用括号括起来:
{: .good}
class MockFoo {
public:
MOCK_METHOD((std::pair<bool, int>), GetPair, ());
MOCK_METHOD(bool, CheckMap, ((std::map<int, double>), bool));
};
请注意,一般来说,用括号包装返回或参数类型是:
无效的 C++。 MOCK_METHOD 删除括号。
解决方案 2 - 定义别名:
{: .good}
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)放在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, (), (override));
MOCK_METHOD(void, Push, (const Elem& x), (override));
};
模拟非虚拟方法
kMock 可以模拟用于 Hi-perf 依赖注入的非虚拟函数。
在这种情况下,您的不是与真实类共享公共基类,而是
模拟类将与真实类无关,但包含带有
相同的签名。模拟非虚拟方法的语法与以下相同
模拟虚拟方法(只是不要添加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));
...
};
请注意,与真实类不同,模拟类没有定义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),而是引入一个
它的接口,并有一个调用 free 函数的具体子类:
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对话以打开文件。现在很容易嘲笑
出函数。
这看起来似乎很麻烦,但实际上你经常会遇到多个问题您可以将相关函数放入同一界面中,因此每个函数 语法开销会低得多。
如果您担心虚拟化带来的性能开销功能,并且分析证实了您的担忧,您可以将其与 模拟非虚拟方法 的配方。
善良、严格和无趣的调用
如果模拟方法没有EXPECT_CALL规范但被调用,我们说它是一个
“无趣的调用”,以及默认操作(可以使用指定
将采用该方法的ON_CALL())。目前,一个无趣的呼叫将
默认情况下也会导致 kMock 打印警告。
然而,有时您可能想忽略这些无趣的调用,并且 有时您可能想将它们视为错误。 kMock 让您做出决定 基于每个模拟对象。
假设您的测试使用模拟类MockFoo:
TEST(...) {
MockFoo mock_foo;
EXPECT_CALL(mock_foo, DoThis());
... code that uses mock_foo ...
}
如果调用 DoThis() 之外的 mock_foo 方法,您将得到一个
警告。但是,如果您重写测试以使用 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仅影响无趣的调用(
方法没有任何期望);它们不会影响意外调用(
方法符合预期,但它们不匹配)。看
了解无趣与意外的呼叫。
但有一些警告(遗憾的是它们是 C++ 的副作用) 限制):
NiceMock<MockFoo>和StrictMock<MockFoo>仅适用于模拟方法 直接在MockFoo类中使用MOCK_METHOD宏定义。 如果在MockFoo的 基类 中定义了模拟方法,则“nice”或 “strict”修饰符可能不会影响它,具体取决于编译器。在 特别是,嵌套“NiceMock”和“StrictMock”(例如 不支持NiceMock<StrictMock<MockFoo> >)。NiceMock<MockFoo>和StrictMock<MockFoo>可能无法正常工作,如果MockFoo的析构函数不是虚拟的。我们想解决这个问题,但是 需要清理现有的测试。
最后,您应该非常谨慎何时使用 naggy 或 strict 模拟,因为它们往往会使测试变得更脆弱且更难以维护。当你 重构您的代码而不改变其外部可见的行为,理想情况下您 不需要更新任何测试。如果你的代码与烦人的模拟交互, 但是,由于您的操作,您可能会开始收到警告垃圾邮件 改变。更糟糕的是,如果您的代码与严格的模拟交互,您的测试可能会开始 失败,你将被迫修复它们。我们的一般建议是使用 漂亮的模拟(还不是默认的)大多数时候,使用 naggy 模拟(当前的 默认)在开发或调试测试时,并 且仅使用严格模拟作为 最后的手段。
简化界面而不破坏现有代码
Sometimes a method has a long list of arguments that is mostly uninteresting. For example:
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 终止的)。如果我们按原样模拟它,则使用模拟将是
尴尬的。但是,如果我们尝试简化此界面,则需要修复所有问题
客户依赖它,这通常是不可行的。
诀窍是重新调度模拟类中的方法:
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));
};
通过使用修剪过的参数列表定义一个新的模拟方法,我们使模拟 类更加人性化。
该技术也可用于使重载方法更适合 嘲笑。例如,当使用重载来实现默认值时 论点:
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()));
模拟具体类的替代方案
通常,您可能会发现自己使用不实现接口的类。在
为了测试使用此类的代码(我们称之为Concrete),您
可能会想将 Concrete 的方法虚拟化,然后模拟它。
尽量不要这样做。
将非虚拟函数设为虚拟是一个重大决定。它创建了一个扩展 子类可以调整类的行为。这会削弱你的控制力 在类上,因为现在维护类不变量变得更加困难。你 仅当有正当理由需要子 类时才应将函数设为虚拟 覆盖它。
直接模拟具体类是有问题的,因为它会产生紧密耦合 在课堂和测试之间 - 课堂上的任何微小变化都可能无效 您的测试并使测试维护变得痛苦。
为了避免此类问题,许多程序员一直在练习编码到 接口("coding to interfaces"):您的代码将定义而不是与Concrete类对话
一个界面并与之交谈。然后你将该接口实现为适配器
Concrete的顶部。在测试中,您可以轻松模拟该界面以观察如何
你的代码正在做。
这种技术会产生一些开销:
- 您支付虚函数调用的成本(通常不是问题)。
- 有更多的抽象可供程序员学习。
然而,除了更好的效果之外,它还可以带来显着的好处。 可测试性:
Concrete的 API 可能不太适合您的问题领域,因为您可能不适合 成为它试图服务的唯一客户。通过设计自己的界面,您 有机会根据您的需要定制它 - 您可以添加更高级别的 功能、重命名等,而不仅仅是修剪类。这 允许您以更自然的方式编写代码(界面的用户), 这意味着它将更具可读性,更易于维护,并且您会更容易 富有成效。- 如果
Concrete的实现需要改变,你不必重写 它无处不在。相反,你可以吸收你的变化 接口的实现,以及您的其他代码和测试将是 免受这种变化的影响。
有人担心,如果每个人都练习这种技术,他们就会结束 写了很多冗余代码。这种担忧是完全可以理解的。 然而,有两个原因可能导致情况并非如此:
- 不同的项目可能需要以不同的方式使用
Concrete,所以最好 他们的界面会有所不同。因此,他们每个人都会有自己的 在“Concrete”之上拥有自己的特定于领域的接口,并且它们不会是 相同的代码。 - 如果有足够多的项目想要使用相同的接口,他们总是可以共享它,
就像他们一直在分享“混凝土”一样。您可以在界面中查看
适配器位于
Concrete附近(可能在contrib中) 子目录)并让许多项目使用它。
您需要针对您的特定问题仔细权衡利弊,但是 我想向您保证,Java 社区已经实践这一点很久了 经过很长时间的考验,这是一种行之有效的技术,适用于多种领域 情况。 :-)
将呼叫委托给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);
}
};
现在您想要模拟此接口,以便您可以对其设置期望。
但是,您还想使用 FakeFoo 作为默认行为,因为复制
它在模拟对象中是,嗯,很多工作。
当您使用 kMock 定义模拟类时,您可以让它委托其默认值 使用以下模式对您已经拥有的假类执行操作:
class MockFoo : public Foo {
public:
// Normal mock method definitions using kMock.
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()中显式设置操作,
假货将被要求这样做:
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(2, n);
}
一些提示:
-
如果需要,您仍然可以通过提供自己的操作来覆盖默认操作
ON_CALL()或在EXPECT_CALL()中使用.WillOnce()/.WillRepeatedly()。 -
在
DelegateToFake()中,只需要委托那些fake的方法 您打算使用的实现。 -
这里讨论的一般技术适用于重载方法,但是 您需要告诉编译器您指的是哪个版本。为了消除歧义 模拟函数(您在
ON_CALL()括号内指定的函数), 使用此技术;消除伪函数的歧义( 放置在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()方法的函数指针的类型。)。 -
必须混合模拟和假通常是出现问题的迹象。 也许您还没有习惯基于交互的测试方式。或者 也许你的界面承担了太多的角色,应该分开。 因此,不要滥用这个。我们只建议将其作为 重构代码时的中间步骤。
关于混合模拟和假的技巧,这里有一个例子说明为什么它可能
这是一个坏兆头:假设你有一个用于低级系统的类System
运营。特别是,它执行文件和 I/O 操作。假设你想要
测试你的代码如何使用System进行 I/O,而你只需要该文件
操作才能正常进行。如果你模拟整个System类,你会
必须为文件操作部分提供一个假的实现,
表明“System”扮演了太多角色。
相反,您可以定义一个FileOps接口和一个IOOps接口,并将System的功能拆分为两个接口。然后你可以模拟IOOps而不模拟FileOps。
将调用委托给真实对象
当使用测试替身(mocks、fakes、stubs 等)时,有时他们的 行为将与真实对象的行为有所不同。这个差异可能是 要么是故意的(例如模拟错误,以便您可以测试错误 处理代码)或无意的。如果你的模拟有不同的行为 如果错误地使用真实的对象,您最终可能会得到通过测试的代码,但是 生产失败。
您可以使用 delegating-to-real 技术来确保您的模拟具有 与真实对象具有相同的行为,同时保留验证调用的能力。 这种技术与 delegating-to-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 ...
这样,kMock 将验证您的代码是否进行了正确的调用(使用正确的 参数,以正确的顺序,调用正确的次数等),以及 真实对象将应答调用(因此行为将与 生产)。这让您两全其美。
将调用委托给父类
理想情况下,您应该对接口进行编码,其方法都是纯虚拟的。在 现实中,有时你确实需要模拟一个不纯的虚拟方法(即, 它已经有一个实现)。例如:
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()。也许您想将其作为存根操作的一部分来执行,或者
也许你的测试根本不需要模拟 Concrete() (但它会是
哦,当你不需要模拟时,必须定义一个新的模拟类,真是太痛苦了
其方法之一)。
您可以通过以下方式在操作内调用 Foo::Concrete():
...
EXPECT_CALL(foo, Concrete).WillOnce([&foo](const char* str) {
return foo.Foo::Concrete(str);
});
或者告诉模拟对象您不想模拟 Concrete():
...
ON_CALL(foo, Concrete).WillByDefault([&foo](const char* str) {
return foo.Foo::Concrete(str);
});
(为什么我们不直接写 { return foo.Concrete(str); }?如果你这样做,
MockFoo::Concrete() 将被调用(并导致无限递归),因为
Foo::Concrete() 是虚拟的。这就是 C++ 的工作原理。)
使用匹配器
精确匹配参数值
您可以准确指定模拟方法需要哪些参数:
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(&MyClass::foo, Contains(f));
};
...
EXPECT_THAT(x, HasFoo("blah"));
选角匹配器
kMock 匹配器是静态类型的,这意味着编译器可以捕获您的
如果您使用错误类型的匹配器,则会出错(例如,如果您使用Eq(5)
匹配string参数)。对你有好处!
然而,有时,您知道自己在做什么,并希望编译器为您提供
有些懈怠。一个例子是你有一个“long”匹配器和参数
你想要匹配的是int。虽然这两种类型并不完全相同,但
使用 Matcher<long> 来匹配 int 并没有什么问题 - 之后
所有,我们可以先将int参数无损地转换为long
将其交给匹配器。
为了支持这种需求,kMock 为您提供了SafeMatcherCast<T>(m)函数。它
将匹配器 m 转换为类型 Matcher<T>。为了确保安全,kMock 会检查
(令“U”为“m”接受的类型:
- 类型
T可以隐式转换为类型U; - 当
T和U都是内置算术类型(bool、整数和 浮点数),从T到U的转换是没有损耗的(在 换句话说,任何可以用T表示的值也可以用U表示); 和 - 当
U是引用时,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)。不同之处在于,只要您有时间,MatcherCast就可以工作
可以通过“static_cast”将“T”类型转换为U类型。
MatcherCast 本质上可以让你绕过 C++ 的类型系统(static_cast 不是
总是安全的,因为它可能会丢弃信息,例如),所以要小心不要
误用/滥用它。
在重载函数之间进行选择
如果您期望调用重载函数,编译器可能需要一些 帮助了解它是哪个重载版本。
要消除在此对象的常量上重载的函数的歧义,请使用
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() 由 kMock 定义,并返回对其参数的 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');
}
根据参数执行不同的操作
当调用模拟方法时,最后匹配的期望仍然是 将选择活动(认为“较新的覆盖较旧的”)。所以,你可以做一个 方法根据其参数值执行不同的操作,如下所示:
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'));
现在,如果调用 foo.DoThis() 的值小于 5,则 'a' 将是
返回;否则将返回b。
将多个参数作为一个整体进行匹配
有时单独匹配参数是不够的。例如,我们
可能想说第一个参数必须小于第二个参数。
With() 子句允许我们将模拟函数的所有参数匹配为
所有的。例如,
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) 来匹配 n 选定的参数(作为
元组)针对m。例如,
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。请注意,在此示例中,无需指定位置匹配器。
作为方便和示例,kMock 提供了一些 2 元组的匹配器,包括上面的“Lt()”匹配器。有关完整列表,请参阅多参数匹配器。
请注意,如果您想将参数传递给您自己的谓词(例如 .With(Args<0, 1>(Truly(&MyPredicate)))),则该谓词必须编写为采用 std::tuple 作为其参数; kMock 会将“n”个选定参数作为一个单个元组传递给谓词。
使用匹配器作为谓词
您是否注意到匹配器只是一个奇特的谓词,它也知道如何
描述自己?许多现有算法将谓词作为参数(例如
那些在 STL 的 <algorithm> 头中定义的),如果 kMock
不允许匹配者参与。
幸运的是,您可以使用一个匹配器,其中需要一元谓词函子
将其包装在 Matches() 函数中。例如,
#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)));
由于您可以使用 kMock 轻松地从简单的匹配器构建复杂的匹配器,因此为您提供了一种方便地构造复合谓词的方法(执行相同的操作
使用 STL 的 <function> 标头非常痛苦)。例如,这是一个任何满足 >= 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)))