GoogleTest 官方文档

Reference:

  1. GoogleTest
  2. github: googletest

在这里插入图片描述

GoogleTest 是 Google 的 C++ 测试和模拟框架。

Quickstart: Building with CMake

1. Set up a project

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 以指向最新版本。

2. Create and run a binary

通过将 GoogleTest 声明为依赖项,您可以在自己的项目中使用 GoogleTest 代码。

例如,创建一个名为 hello_test.cc 的文件。在 my_project 目录下添加以下内容:

#include <gtest/gtest.h>

// 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 Primer

1. Introduction: Why GoogleTest?

GoogleTest 帮助您编写更好的 C++ 测试。
GoogleTest 是由测试技术团队根据 Google 的特定需求和限制开发的测试框架。无论你是在 Linux、Windows 还是 Mac 上工作,如果你编写 C++ 代码,GoogleTest 都可以帮助你。它支持任何类型的测试,不仅仅是单元测试。

那么,什么是一个好的测试,GoogleTest 是如何发挥作用的呢?我们相信:

  1. 测试应该是独立的可重复的。调试一个由于其他测试而成功或失败的测试是一件痛苦的事情。GoogleTest 通过在不同的对象上运行每个测试来隔离测试。当测试失败时,GoogleTest 允许您单独运行它以进行快速调试。
  2. 测试应该组织清晰,并反映被测试代码的结构。GoogleTest 将相关测试分组到可以共享数据和子例程的测试套件中。这种通用模式易于识别,并且使测试易于维护。当人们切换项目并开始使用新的代码库时,这种一致性尤其有用。
  3. 测试应该是可移植可重用的。谷歌有很多与平台无关的代码;它的测试也应该是平台中立的。GoogleTest 可以在不同的操作系统上工作,使用不同的编译器,有或没有异常,因此 GoogleTest 测试可以使用各种配置。
  4. 当测试失败时,它们应该提供尽可能多的有关问题的信息。GoogleTest 不会在第一次测试失败时停止。相反,它只停止当前测试并继续进行下一个测试。还可以设置报告非致命故障的测试,然后继续当前测试。因此,您可以在单个运行-编辑-编译周期中检测和修复多个错误。
  5. 测试框架应该把测试编写者从日常琐事中解放出来,让他们专注于测试内容。GoogleTest 自动跟踪所有定义的测试,并且不需要用户为了运行它们而枚举它们。
  6. 测试应该很快。使用 GoogleTest,您可以在测试中重用共享资源,并且只需为设置/拆除支付一次费用,而不会使测试相互依赖。

由于 GoogleTest 基于流行的 xUnit 架构,如果您以前使用过 JUnit 或 PyUnit,您会感到很自在。如果没有,你需要花大约10分钟来学习基础知识并开始。所以我们走吧!

2. Basic Concepts

使用 GoogleTest 时,首先编写断言(assertions),即检查条件是否为真的语句。断言的结果可以是 成功(success)非致命错误(nonfatal failure)致命错误(fatal failure)。如果发生 fatal failure,则终止当前函数;否则程序将继续正常运行。

测试(test)使用断言来验证被测试代码的表现。如果测试崩溃或断言失败,则测试 fails;否则 succeeds。

测试套件(test suite)包含一个或多个测试。您应该将测试分组到反映测试代码结构的测试套件中。当测试套件中的多个测试需要共享公共对象和子例程时,您可以将它们放入 test fixture 类中。

一个测试程序(test program)可以包含多个测试套件。

现在我们将解释如何编写测试程序,从单个断言级别开始,逐步构建测试和测试套件。

3. Assertions

GoogleTest 断言是类似于函数调用的宏。通过断言类或函数的行为来测试类或函数。当断言失败时,GoogleTest 打印断言的源文件和行号位置,以及失败消息。您也可以提供一个自定义的失败消息,它将被附到 GoogleTest的消息中。

断言成对出现,它们测试相同的东西,但对当前函数有不同的影响。

  • ASSERT_* 版本失败时会产生致命错误并中止当前函数
  • EXPECT_* 版本会产生非致命错误,不会 abort 当前函数。通常首选 EXPECT_*,因为它们允许在测试中报告多个失败。但是,如果在断言失败时继续下去没有意义,则应该使用 ASSERT_*

由于失败的 ASSERT_* 会立即从当前函数返回,可能会跳过后面的清理代码,因此可能会导致空间泄漏。根据泄漏的性质,它可能值得修复,也可能不值得修复——因此,如果除了断言错误之外还出现堆检查器错误,请记住这一点。

要提供自定义的失败消息,只需使用 << 操作符或此类操作符的序列将其流式传输到宏中。请看下面的例子,使用 ASSERT_EQEXPECT_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

4. Simple Tests

创建一个测试:

  1. 使用 TEST() 宏来定义和命名测试函数。这些是没有返回值的普通 C++ 函数
  2. 在这个函数中,与您想要包含的任何有效的 C++ 语句一起,使用各种 GoogleTest 断言来检查值
  3. 测试结果由断言决定;如果测试中的任何断言失败(无论是致命的还是非致命的),或者如果测试崩溃,则整个测试失败。否则,则成功
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)。在上面的示例中,我们有两个测试,HandlesZeroInputHandlesPositiveInput,它们属于同一个测试套件 FactorialTest

在命名测试套件和测试时,您应该遵循与命名函数和类相同的约定。

5. Test Fixtures: Using the Same Data Configuration for Multiple Tests

如果您发现自己要编写两个或更多操作在相似数据的测试,那么您可以使用测试夹具(test fixture)。这允许您为几个不同的测试重用相同的对象配置

创建一个 fixture:

  1. ::testing::Test 派生一个类。从 protected: 开始它的主体,因为我们要从子类访问fixture成员。
  2. 在类中,声明计划使用的任何对象。
  3. 如果有必要,编写一个默认构造函数或 SetUp() 函数来为每个测试准备对象。一个常见的错误是将 SetUp() 拼写为带有一个小 uSetup()在 C++ 11 中使用 override 以确保拼写正确
  4. 如果有必要,编写析构函数或 TearDown() 函数来释放在 SetUp() 中分配的任何资源。要了解什么时候应该使用构造函数/析构函数,什么时候应该使用 SetUp()/TearDown(),请阅读 FAQ
  5. 如果需要,定义要共享的测试子例程。

当使用 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,这将在 nNULL 时导致段错误。
当这些测试运行时,将发生以下情况:

  1. GoogleTest 构造一个 QueueTest 对象(我们称它为 t1)。
  2. t1.SetUp() 初始化 t1
  3. 第一个测试(IsEmptyInitially)在 t1 上运行。
  4. t1.TearDown() 在测试结束后进行清理。
  5. t1 被析构。
  6. 在另一个 QueueTest 对象上重复上述步骤,这次运行 DequeueWorks 测试。

6. Invoking the Tests

TEST()TEST_F() 隐式地将它们的测试注册到 GoogleTest。因此,与许多其他 C++ 测试框架不同,您不必为了运行它们而重新列出所有已定义的测试。

定义测试之后,可以使用 RUN_ALL_TESTS() 运行它们,如果所有测试都成功,则返回 0,否则返回 1。注意,RUN_ALL_TESTS() 运行链接单元中的所有测试—它们可以来自不同的测试套件,甚至是不同的源文件。

当调用 RUN_ALL_TESTS() 宏时:

  • 保存所有 GoogleTest 标志的状态。
  • 为第一个测试创建一个测试夹具对象(test fixture object)。
  • 通过 SetUp() 初始化它。
  • 在 fixture 对象上运行测试。
  • 通过 TearDown() 清理夹具。
  • 删除 fixture。
  • 恢复所有 GoogleTest 标志的状态。
  • 对下一个测试重复上述步骤,直到所有测试都运行完毕。

如果发生致命故障,则跳过后续步骤。

IMPORTANT:不要忽略 RUN_ALL_TESTS() 的返回值,否则将会得到一个编译错误。这种设计的基本原理是,自动化测试服务根据退出代码(而不是标准输出/标准错误输出)确定测试是否通过;因此 main() 函数必须返回 RUN_ALL_TESTS() 的值

另外,您应该只调用一次 RUN_ALL_TESTS()。多次调用它与一些高级 GoogleTest 功能(例如,线程安全死亡测试)相冲突,因此不支持。

7. Writing the main() Function

大多数用户不需要编写自己的 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 库链接起来,就可以了。

8. Known Limitations

Google Test 的设计是线程安全的。在 pthreads 库可用的系统上,该实现是线程安全的。目前在其他系统(例如Windows)上同时使用两个线程中的 Google Test 断言是不安全的。在大多数测试中,这不是问题,因为断言通常是在主线程中完成的。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泠山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值