学习如何使用cpp工程如何使用GoogleTest框架编写测试。
GoogleTest这样定义了一个好的测试
- 测试独立并且可以重复的。即一个测试不会依赖其他测试。并且每个测试的结果是确定的。
- 测试要被良好地组织并且能够反映被测试代码结构。GoogleTest将有相关性的测试分为一组test suites,可以共享数据和辅助的函数。
- 测试需要可移植和重用。
- 测试失败要提供足够多的信息
- 测试框架要让编写测试的人只关注测试的内容。
- 测试要很快。google test可以在测试之间共享资源
GoogleTest术语注意事项
在google test框架中,术语test, test case分别对应ISTQB定义的test case , test suites。
不过google test也在使用test suites替代test case了,并提供了对应的api,旧的api将会过期。
在google test中有如下几个概念
- Tests:使用断言来验证被测试代码的行为,如果测试崩溃或者断言失败,则测试通过;否则成功。
- Test suite:包含一个或多个test。当test suite中的多个测试之间需要共享公共数据对象和辅助函数,可以将它们放在test fixture当中。
- Test program:一个test program可以包含多个test suites。
编写简单的测试
使用TEST宏来编写测试代码,宏结构示例如下
TEST(TestSuiteName, TestName) {
... test body ...
}TestSuiteName相同的Test属于同一个Test Suite,TestName则是具体这个测试的名称。 在内部使用断言来验证结果。断言失败则整个测试失败。两者都必须是有效的c++标识符,并且不能包含任何下划线。
TEST定义的测试全名由TestSuiteName和TestName组合而来。
GoogleTest提供了丰富的断言,可以参考官方文档Assertions。
Test Fixtures:测试之间共享数据
Test suite中的不同测试想要使用共同的数据,就可以通过Test Fixtures来解决。
首先要创建一个fixture类,流程如下:
- 创建一个派生于
testing::Test的类,所有的内容都放在protected下,因为需要自类能够访问到。 - 在这个类型生命要共享的对象
- 使用默认构造or
SetUp()初始化好要共享的数据。 - 使用默认析构函数or
TearDown()来释放资源 - 共享的辅助函数也可以添加到里面
使用TEST_F宏而非TEST宏来编写测试,这样在内部就可以使用上面创建的fixture中的共享数据了。宏结构如下
TEST_F(TestFixtureClassName, TestName) {
... test body ...
}其中第一个参数必须是前面创建的fixture类的类名。
TEST_F宏定义的每个测试使用的都是一个新的fixture对象,并且立即执行SetUp进行初始化,然后再执行测试,接着执行TearDown清理资源,最后删除fxiture对象。
现有一段代码需要进行测试
template <typename E> // E is the element type.
class Queue {
public:
Queue();
void Enqueue(const E& element);
E* Dequeue(); // Returns NULL if the queue is empty.
size_t size() const;
...
};按照前面所说的流程,创建一个fixture
class QueueTest : public testing::Test {
protected:
QueueTest() {
// q0_ remains empty
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// ~QueueTest() override = default;
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};这样在为Queue编写测试代码的时候,就可以直接使用QueueTest中的内容了。
TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(q0_.size(), 0);
}
TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(n, nullptr);
n = q1_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 1);
EXPECT_EQ(q1_.size(), 0);
delete n;
n = q2_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 2);
EXPECT_EQ(q2_.size(), 1);
delete n;
}运行测试
TEST和TEST_F都会隐式将测试注册到GoogleTest当中。要执行这些测试则是需要执行另一个宏RUN_ALL_TESTS(),这个宏会执行连接单元中的所有测试。执行结果为0表示测试成功。
运行宏的执行流程是这样的
- 保存GoogleTest的flags状态
- 为第一个测试创建fixture
- 调用
SetUp初始化 - 运行fixture上的测试
- 调用
TearDown清理fixture - 删除fixture
- 恢复GoogleTest的flags状态
- 重复上述流程直到所有的测试运行完成。
编写main函数
所有的测试用例最终都要编译成一个可执行文件进行执行,因此同样需要编写main函数。
上面运行测试使用的RUN_ALL_TESTS()宏就要在main函数中调用。
大多数情况用户也不必编写main函数,而是连接到gtest_main方法,这里定义了合适的入口点。当然如果需要在执行gtest做一些自定义内部,那么编写main方法还是有必要的。
main函数的样板代码如下
#include "this/package/foo.h"
#include <gtest/gtest.h>
namespace my {
namespace project {
namespace {
// The fixture for testing class Foo.
class FooTest : public testing::Test {
protected:
// You can remove any or all of the following functions if their bodies would
// be empty.
FooTest() {
// You can do set-up work for each test here.
}
~FooTest() override {
// You can do clean-up work that doesn't throw exceptions here.
}
// If the constructor and destructor are not enough for setting up
// and cleaning up each test, you can define the following methods:
void SetUp() override {
// Code here will be called immediately after the constructor (right
// before each test).
}
void TearDown() override {
// Code here will be called immediately after each test (right
// before the destructor).
}
// Class members declared here can be used by all tests in the test suite
// for Foo.
};
// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
const std::string input_filepath = "this/package/testdata/myinputfile.dat";
const std::string output_filepath = "this/package/testdata/myoutputfile.dat";
Foo f;
EXPECT_EQ(f.Bar(input_filepath, output_filepath), 0);
}
// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
// Exercises the Xyz feature of Foo.
}
} // namespace
} // namespace project
} // namespace my
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}testing::InitGoogleTest用于从命令行中解析处GoogleTest flags,可以用来控制测试程序的行为,这这下一小节进一步说明。
要是不需要对main方法做任何改动,那么直接链接gtest_main这个库即可,库里提供了默认的main函数实现。
编写MOCK测试
mock一个普通的class
现在有如下的类
class Foo {
public:
virtual ~Foo();
virtual int GetSize() const = 0;
virtual string Describe(const char* name) = 0;
virtual string Describe(int type) = 0;
virtual bool Process(Bar elem, int count) = 0;
};我们要对其进行mock,需要注意的是,上面被mock的Foo的析构函数必须是虚函数。
这样可以为Foo创建一个子类,使用gmock的宏来mock需要的函数
#include <gmock/gmock.h>
class MockFoo : public Foo {
public:
MOCK_METHOD(int, GetSize, (), (const, override));
MOCK_METHOD(string, Describe, (const char* name), (override));
MOCK_METHOD(string, Describe, (int type), (override));
MOCK_METHOD(bool, Process, (Bar elem, int count), (override));
};在上面的基础上,还可以进一步创建三种模式的Mock子类
using ::testing::NiceMock;
using ::testing::NaggyMock;
using ::testing::StrictMock;
NiceMock<MockFoo> nice_foo; // 忽略不感兴趣的函数调用
NaggyMock<MockFoo> naggy_foo; // 对不感兴趣的函数调用进行告警
StrictMock<MockFoo> strict_foo; // 不感兴趣的函数调用会失败mock一个类模板
模板类如下
template <typename Elem>
class StackInterface {
public:
virtual ~StackInterface();
virtual int GetSize() const = 0;
virtual void Push(const Elem& x) = 0;
};同样的,析构函数函数也要是虚函数。
template <typename Elem>
class MockStack : public StackInterface<Elem> {
public:
MOCK_METHOD(int, GetSize, (), (const, override));
MOCK_METHOD(void, Push, (const Elem& x), (override));
};mock的流程和前面是一致的。
在test中使用mock类
使用流程通常如下
- 导入需要使用的gMock 名。所有gMock符号都在
testingnamespace中,除非它们是宏或以其他方式注明。 - 创建mock对象
- (可选)设置mock对象的默认行为
- 设置mock对象的expectation行为
- 使用断言检查mock对象执行结果
- mock对象销毁的时候,gMock会自动检查前面设置的expectation是否达成 一个使用示例如下
using ::testing::Return; // #1
TEST(BarTest, DoesThis) {
// 创建mock对象
MockFoo foo; // #2
// 设置mock对象的默认行为
ON_CALL(foo, GetSize()) // #3
.WillByDefault(Return(1));
// ... other default actions ...
// 设置执行结果的预期
EXPECT_CALL(foo, Describe(5)) // #4
.Times(3)
.WillRepeatedly(Return("Category 5"));
// ... other expectations ...
// 执行一些其他的断言检测
EXPECT_EQ(MyProductionFunction(&foo), "good"); // #5
// mock对象释放,自动检测expectataion是否符合预期
} // #6