Reference:
GoogleTest 是 Google 的 C++ 测试和模拟框架。
CMake 使用一个名为 CMakeLists.txt
的文件来配置项目的构建系统。您将使用该文件设置项目并声明对 GoogleTest 的依赖项。
首先,为你的项目创建一个目录:
$ mkdir my_project && cd my_project
接下来,您将创建 CMakeLists.txt
文件,并声明对 GoogleTest 的依赖。在CMake生态系统中有很多表达依赖关系的方法;在这个快速入门中,您将使用 FetchContent
CMake 模块。为此,在你的项目目录(my_project
)中,创建一个名为 CMakeLists.txt
的文件,内容如下:
cmake_minimum_required(VERSION 3.14)
project(my_project)
# GoogleTest requires at least C++14
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
上面的配置声明了对从 GitHub 下载的 GoogleTest 的依赖。在上面的例子中, 03597a01ee50ed33e9dfd640b249b4be3799d395 是要使用的 GoogleTest 版本的 Git commit hash;我们建议经常更新 hash 以指向最新版本。
通过将 GoogleTest 声明为依赖项,您可以在自己的项目中使用 GoogleTest 代码。
例如,创建一个名为 hello_test.cc
的文件。在 my_project 目录下添加以下内容:
#include
// Demonstrate some basic assertions.
TEST(HelloTest, BasicAssertions) {
// Expect two strings not to be equal.
EXPECT_STRNE("hello", "world");
// Expect equality.
EXPECT_EQ(7 * 6, 42);
}
GoogleTest 提供了用于测试代码行为的断言。上面的示例包括主要的 GoogleTest 头文件,并演示了一些基本的断言。
要构建代码,在 CMakeLists.txt
文件的末尾添加以下代码:
enable_testing()
add_executable(
hello_test
hello_test.cc
)
target_link_libraries(
hello_test
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(hello_test)
我自己的配置如下:
project(gtest_test)
cmake_minimum_required(VERSION 3.18)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(hello_test
hello_test.cc
)
target_link_libraries(hello_test
gtest_main
)
include(GoogleTest)
gtest_discover_tests(hello_test)
如果不加上 include(FetchContent)
那部分会报以下错误,应该是我这里的 gtest 版本不一致导致的。使用 gtest_main
是因为没有自己写 main 函数:
上面的配置可以在 CMake 中进行测试,声明你想要构建的 C++ 测试二进制文件(hello_test
),并将其链接到GoogleTest (gtest_main
)。最后两行使 CMake 的测试运行器能够使用 GoogleTest
CMake模块(链接将介绍 gtest_discover_tests
的使用方法)发现二进制文件中包含的测试。
现在你可以构建并运行测试了:
my_project$ cmake -S . -B build
-- The C compiler identification is GNU 10.2.1
-- The CXX compiler identification is GNU 10.2.1
...
-- Build files have been written to: .../my_project/build
my_project$ cmake --build build
Scanning dependencies of target gtest
...
[100%] Built target gmock_main
my_project$ cd build && ctest
Test project .../my_project/build
Start 1: HelloTest.BasicAssertions
1/1 Test #1: HelloTest.BasicAssertions ........ Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.01 sec
自己运行的效果如下:
如果将上述测试用例的 42
改成 41
,则运行结果为:
GoogleTest 帮助您编写更好的 C++ 测试。
GoogleTest 是由测试技术团队根据 Google 的特定需求和限制开发的测试框架。无论你是在 Linux、Windows 还是 Mac 上工作,如果你编写 C++ 代码,GoogleTest 都可以帮助你。它支持任何类型的测试,不仅仅是单元测试。
那么,什么是一个好的测试,GoogleTest 是如何发挥作用的呢?我们相信:
由于 GoogleTest 基于流行的 xUnit 架构,如果您以前使用过 JUnit 或 PyUnit,您会感到很自在。如果没有,你需要花大约10分钟来学习基础知识并开始。所以我们走吧!
使用 GoogleTest 时,首先编写断言(assertions)
,即检查条件是否为真的语句。断言的结果可以是 成功(success)
、 非致命错误(nonfatal failure)
或 致命错误(fatal failure)
。如果发生 fatal failure,则终止当前函数;否则程序将继续正常运行。
测试(test)
使用断言来验证被测试代码的表现。如果测试崩溃或断言失败,则测试 fails;否则 succeeds。
测试套件(test suite)
包含一个或多个测试。您应该将测试分组到反映测试代码结构的测试套件中。当测试套件中的多个测试需要共享公共对象和子例程时,您可以将它们放入 test fixture 类中。
一个测试程序(test program)
可以包含多个测试套件。
现在我们将解释如何编写测试程序,从单个断言级别开始,逐步构建测试和测试套件。
GoogleTest 断言是类似于函数调用的宏。通过断言类或函数的行为来测试类或函数。当断言失败时,GoogleTest 打印断言的源文件和行号位置,以及失败消息。您也可以提供一个自定义的失败消息,它将被附到 GoogleTest的消息中。
断言成对出现,它们测试相同的东西,但对当前函数有不同的影响。
ASSERT_*
版本失败时会产生致命错误,并中止当前函数。EXPECT_*
版本会产生非致命错误,不会 abort 当前函数。通常首选 EXPECT_*
,因为它们允许在测试中报告多个失败。但是,如果在断言失败时继续下去没有意义,则应该使用 ASSERT_*
。由于失败的 ASSERT_*
会立即从当前函数返回,可能会跳过后面的清理代码,因此可能会导致空间泄漏。根据泄漏的性质,它可能值得修复,也可能不值得修复——因此,如果除了断言错误之外还出现堆检查器错误,请记住这一点。
要提供自定义的失败消息,只需使用 <<
操作符或此类操作符的序列将其流式传输到宏中。请看下面的例子,使用 ASSERT_EQ
和 EXPECT_EQ
宏来验证值是否相等:
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";
for (int i = 0; i < x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}
任何可以流到 ostream
的内容都可以流到断言宏中——特别是 C 字符串和 string
对象。如果将一个宽字符串(wchar_t*
, Windows 上 UNICODE 模式下的 TCHAR*
,或 std::wstring
)流式传输到断言中,则在打印时将其转换为 UTF-8。
上面代码若 x 和 y 不相等,会报出以下错误,这里字符串就被打印出来了:
GoogleTest 提供了一组断言,用于以各种方式验证代码的行为。您可以检查布尔条件、基于关系运算符比较值、验证字符串值、浮点值等等。甚至还有断言,通过提供自定义谓词,使您能够验证更复杂的状态。有关 GoogleTest 提供的断言的完整列表,请参阅 Assertions Reference。
创建一个测试:
TEST()
宏来定义和命名测试函数。这些是没有返回值的普通 C++ 函数。TEST(TestSuiteName, TestName) {
... test body ...
}
TEST()
参数从一般到特定。第一个参数 是测试套件的名称,第二个参数 是测试套件中的测试名称。两个名称都必须是有效的 C++ 标识符,并且不应该包含任何下划线(_
)。测试的全名由它所包含的测试套件和它的单独名称组成。来自不同测试套件的测试可以具有相同的单独名称。
例如,让我们取一个简单的整数函数:
int Factorial(int n); // Returns the factorial of n
此函数的测试套件可能如下所示:
// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(Factorial(0), 1);
}
// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(Factorial(1), 1);
EXPECT_EQ(Factorial(2), 2);
EXPECT_EQ(Factorial(3), 6);
EXPECT_EQ(Factorial(8), 40320);
}
GoogleTest 按测试套件对测试结果进行分组,因此逻辑相关的测试应在同一测试套件中;换句话说,它们的 TEST()
的第一个参数应该是相同的(均为 FactorialTest)。在上面的示例中,我们有两个测试,HandlesZeroInput
和 HandlesPositiveInput
,它们属于同一个测试套件 FactorialTest
。
在命名测试套件和测试时,您应该遵循与命名函数和类相同的约定。
如果您发现自己要编写两个或更多操作在相似数据的测试,那么您可以使用测试夹具(test fixture)
。这允许您为几个不同的测试重用相同的对象配置。
创建一个 fixture:
::testing::Test
派生一个类。从 protected:
开始它的主体,因为我们要从子类访问fixture成员。SetUp()
函数来为每个测试准备对象。一个常见的错误是将 SetUp()
拼写为带有一个小 u
的 Setup()
— 在 C++ 11 中使用 override
以确保拼写正确。TearDown()
函数来释放在 SetUp()
中分配的任何资源。要了解什么时候应该使用构造函数/析构函数,什么时候应该使用 SetUp()/TearDown()
,请阅读 FAQ。当使用 fixture 时,使用 TEST_F()
而不是 TEST()
,因为它允许您访问测试 fixture 中的对象和子例程:
TEST_F(TestFixtureClassName, TestName) {
... test body ...
}
与 TEST()
不同,在 TEST_F()
中,第一个参数必须是测试 fixture 类的名称。(_F
代表“Fixture”)。没有为此宏指定测试套件名称。
不幸的是,C++ 宏系统不允许我们创建一个可以处理这两种类型测试的宏。使用错误的宏会导致编译器错误。
此外,在 TEST_F()
中使用测试 fixture 类之前,必须首先定义它,否则会得到编译器错误 “virtual outside class declaration”
。
对于使用 TEST_F()
定义的每个测试,GoogleTest 将在运行时创建一个新的测试 fixture,立即通过 SetUp()
初始化它,运行测试,通过调用 TearDown()
进行清理,然后删除测试 fixture。请注意,同一测试套件中的不同测试具有不同的测试夹具对象,并且 GoogleTest 总是在创建下一个测试夹具之前删除一个测试夹具。GoogleTest 不会为多个测试重用相同的测试夹具。一个测试对夹具所做的任何更改都不会影响其他测试。
作为一个例子,让我们为一个名为 Queue
的 FIFO 队列类编写测试,它有以下接口:
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 类。按照惯例,您应该给它命名为 FooTest
,其中 Foo
是被测试的类。
class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
// q0_ remains empty
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// void TearDown() override {}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
在这种情况下,不需要 TearDown()
,因为除了析构函数已经完成的工作外,我们不需要在每次测试之后清理。
现在我们将使用 TEST_F()
和这个 fixture 编写测试。
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;
}
上面的代码同时使用 ASSERT_*
和 EXPECT_*
断言。经验法则是,当您希望测试在断言失败后继续发现更多错误时,使用 EXPECT_*
,而在失败后继续测试没有意义时,使用 ASSERT_*
。例如,Dequeue
测试中的第二个断言是 ASSERT_NE(n, nullptr)
,因为我们稍后需要解引用指针 n
,这将在 n
为 NULL
时导致段错误。
当这些测试运行时,将发生以下情况:
QueueTest
对象(我们称它为 t1
)。t1.SetUp()
初始化 t1
。IsEmptyInitially
)在 t1
上运行。t1.TearDown()
在测试结束后进行清理。t1
被析构。QueueTest
对象上重复上述步骤,这次运行 DequeueWorks
测试。TEST()
和 TEST_F()
隐式地将它们的测试注册到 GoogleTest。因此,与许多其他 C++ 测试框架不同,您不必为了运行它们而重新列出所有已定义的测试。
定义测试之后,可以使用 RUN_ALL_TESTS()
运行它们,如果所有测试都成功,则返回 0
,否则返回 1
。注意,RUN_ALL_TESTS()
运行链接单元中的所有测试—它们可以来自不同的测试套件,甚至是不同的源文件。
当调用 RUN_ALL_TESTS()
宏时:
SetUp()
初始化它。TearDown()
清理夹具。如果发生致命故障,则跳过后续步骤。
IMPORTANT:不要忽略 RUN_ALL_TESTS()
的返回值,否则将会得到一个编译错误。这种设计的基本原理是,自动化测试服务根据退出代码(而不是标准输出/标准错误输出)确定测试是否通过;因此 main()
函数必须返回 RUN_ALL_TESTS()
的值。
另外,您应该只调用一次 RUN_ALL_TESTS()
。多次调用它与一些高级 GoogleTest 功能(例如,线程安全死亡测试)相冲突,因此不支持。
大多数用户不需要编写自己的 main
函数,而是使用 gtest_main
(而不是gtest
)链接,gtest_main
定义了一个合适的入口点(如果自己写 main
函数就在 target_link_libraries
内链接 gtest
,如果不用自己编写的 main
,则链接 gtest_main
)。有关详细信息,请参阅本节的末尾。本节的其余部分只适用于在测试运行之前需要做一些定制的事情,而这些事情不能在 fixture 和测试套件的框架内表示。
如果编写自己的 main
函数,它应该返回 RUN_ALL_TESTS()
的值。
你可以从这个样板文件开始:
#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,并移除所有可识别的 flags。这允许用户通过各种 flags 来控制测试程序的行为,我们将在 AdvancedGuide 中介绍。必须在调用 RUN_ALL_TESTS()
之前调用此函数,否则将无法正确初始化标志。
在Windows上,InitGoogleTest()
也适用于宽字符串,因此它也可以在 UNICODE
模式下编译的程序中使用。
但也许你认为编写所有这些主要函数是太多的工作?我们完全同意你的观点,这就是为什么 Google Test 提供了 main()
的基本实现。如果它符合您的需要,那么只需将您的测试与 gtest_main
库链接起来,就可以了。
Google Test 的设计是线程安全的。在 pthreads
库可用的系统上,该实现是线程安全的。目前在其他系统(例如Windows)上同时使用两个线程中的 Google Test 断言是不安全的。在大多数测试中,这不是问题,因为断言通常是在主线程中完成的。