> For the complete documentation index, see [llms.txt](https://chenxiaowei.gitbook.io/cmake-cookbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://chenxiaowei.gitbook.io/cmake-cookbook/4.0-chinese/4.3-chinese.md).

# 4.3 使用Google Test库进行单元测试

**NOTE**:*此示例代码可以在* <https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-04/recipe-03> *中找到，包含一个C++的示例。该示例在CMake 3.11版(或更高版本)中是有效的，并且已经在GNU/Linux、macOS和Windows上进行过测试。在代码库中，有一个支持CMake 3.5的例子。*

本示例中，我们将演示如何在CMake的帮助下使用Google Test框架实现单元测试。与前一个配置相比，Google Test框架不仅仅是一个头文件，也是一个库，包含两个需要构建和链接的文件。可以将它们与我们的代码项目放在一起，但是为了使代码项目更加轻量级，我们将选择在配置时，下载一个定义良好的Google Test，然后构建框架并链接它。我们将使用较新的`FetchContent`模块(从CMake版本3.11开始可用)。第8章中会继续讨论`FetchContent`，在这里将讨论模块在底层是如何工作的，并且还将演示如何使用`ExternalProject_Add`进行模拟。此示例的灵感来自(改编自) <https://cmake.org/cmake/help/v3.11/module/FetchContent.html> 示例。

## 准备工作

`main.cpp`、`sum_integers.cpp`和`sum_integers.hpp`与之前相同，修改`test.cpp`:

```cpp
#include "sum_integers.hpp"
#include "gtest/gtest.h"

#include <vector>

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

TEST(example, sum_zero) {
  auto integers = {1, -1, 2, -2, 3, -3};
  auto result = sum_integers(integers);
  ASSERT_EQ(result, 0);
}

TEST(example, sum_five) {
  auto integers = {1, 2, 3, 4, 5};
  auto result = sum_integers(integers);
  ASSERT_EQ(result, 15);
}
```

如上面的代码所示，我们显式地将`gtest.h`，而不将其他Google Test源放在代码项目存储库中，会在配置时使用`FetchContent`模块下载它们。

## 具体实施

下面的步骤描述了如何设置`CMakeLists.txt`，使用GTest编译可执行文件及其相应的测试:

1. 与前两个示例相比，`CMakeLists.txt`的开头基本没有变化，CMake 3.11才能使用`FetchContent`模块:

   ```
   # set minimum cmake version
   cmake_minimum_required(VERSION 3.11 FATAL_ERROR)

   # project name and language
   project(recipe-03 LANGUAGES CXX)

   # require C++11
   set(CMAKE_CXX_STANDARD 11)
   set(CMAKE_CXX_EXTENSIONS OFF)
   set(CMAKE_CXX_STANDARD_REQUIRED ON)
   set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

   # example library
   add_library(sum_integers sum_integers.cpp)

   # main code
   add_executable(sum_up main.cpp)
   target_link_libraries(sum_up sum_integers)
   ```
2. 然后引入一个`if`，检查`ENABLE_UNIT_TESTS`。默认情况下，它为`ON`，但有时需要设置为`OFF`，以免在没有网络连接时，也能使用Google Test:

   ```
   option(ENABLE_UNIT_TESTS "Enable unit tests" ON)
   message(STATUS "Enable testing: ${ENABLE_UNIT_TESTS}")

   if(ENABLE_UNIT_TESTS)
       # all the remaining CMake code will be placed here
   endif()
   ```
3. `if`内部包含`FetchContent`模块，声明要获取的新内容，并查询其属性:

   ```
   include(FetchContent)

   FetchContent_Declare(
     googletest
     GIT_REPOSITORY https://github.com/google/googletest.git
     GIT_TAG release-1.8.0
   )

   FetchContent_GetProperties(googletest)
   ```
4. 如果内容还没有获取到，将尝试获取并配置它。这需要添加几个可以链接的目标。本例中，我们对`gtest_main`感兴趣。该示例还包含一些变通方法，用于使用在Visual Studio下的编译:

   ```
   if(NOT googletest_POPULATED)
     FetchContent_Populate(googletest)

     # Prevent GoogleTest from overriding our compiler/linker options
     # when building with Visual Studio
     set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
     # Prevent GoogleTest from using PThreads
     set(gtest_disable_pthreads ON CACHE BOOL "" FORCE)

     # adds the targers: gtest, gtest_main, gmock, gmock_main
     add_subdirectory(
       ${googletest_SOURCE_DIR}
       ${googletest_BINARY_DIR}
       )

     # Silence std::tr1 warning on MSVC
     if(MSVC)
       foreach(_tgt gtest gtest_main gmock gmock_main)
         target_compile_definitions(${_tgt}
           PRIVATE
               "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING"
         )
       endforeach()
     endif()
   endif()
   ```
5. 然后，使用`target_sources`和`target_link_libraries`命令，定义`cpp_test`可执行目标并指定它的源文件:

   ```
   add_executable(cpp_test "")

   target_sources(cpp_test
     PRIVATE
         test.cpp
     )

   target_link_libraries(cpp_test
     PRIVATE
       sum_integers
       gtest_main
     )
   ```
6. 最后，使用`enable_test`和`add_test`命令来定义单元测试:

   ```
   enable_testing()

   add_test(
     NAME google_test
     COMMAND $<TARGET_FILE:cpp_test>
     )
   ```
7. 现在，准备配置、构建和测试项目:

   ```
   $ mkdir -p build
   $ cd build
   $ cmake ..
   $ cmake --build .
   $ ctest

   Test project /home/user/cmake-cookbook/chapter-04/recipe-03/cxx-example/build
       Start 1: google_test
   1/1 Test #1: google_test ...................... Passed 0.00 sec

   100% tests passed, 0 tests failed out of 1

   Total Test time (real) = 0.00 sec
   ```
8. 可以直接运行`cpp_test`:

   ```
   $ ./cpp_test

   [==========] Running 2 tests from 1 test case.
   [----------] Global test environment set-up.
   [----------] 2 tests from example
   [ RUN ] example.sum_zero
   [ OK ] example.sum_zero (0 ms)
   [ RUN ] example.sum_five
   [ OK ] example.sum_five (0 ms)
   [----------] 2 tests from example (0 ms total)

   [----------] Global test environment tear-down
   [==========] 2 tests from 1 test case ran. (0 ms total)
   [ PASSED ] 2 tests.
   ```

## 工作原理

`FetchContent`模块支持通过`ExternalProject`模块，在配置时填充内容，并在其3.11版本中成为CMake的标准部分。而`ExternalProject_Add()`在构建时(见第8章)进行下载操作，这样`FetchContent`模块使得构建可以立即进行，这样获取的主要项目和外部项目(在本例中为Google Test)仅在第一次执行CMake时调用，使用`add_subdirectory`可以嵌套。

为了获取Google Test，首先声明外部内容:

```
include(FetchContent)

FetchContent_Declare(
    googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG release-1.8.0
)
```

本例中，我们获取了一个带有特定标记的Git库(release-1.8.0)，但是我们也可以从Subversion、Mercurial或HTTP(S)源获取一个外部项目。有关可用选项，可参考相应的`ExternalProject_Add`命令的选项，网址是<https://cmake.org/cmake/help/v3.11/module/ExternalProject.html> 。

调用`FetchContent_Populate()`之前，检查是否已经使用`FetchContent_GetProperties()`命令处理了内容填充；否则，调用`FetchContent_Populate()`超过一次后，就会抛出错误。

`FetchContent_Populate(googletest)`用于填充源并定义`googletest_SOURCE_DIR`和`googletest_BINARY_DIR`，可以使用它们来处理Google Test项目(使用`add_subdirectory()`，因为它恰好也是一个CMake项目):

```
add_subdirectory(
  ${googletest_SOURCE_DIR}
  ${googletest_BINARY_DIR}
  )
```

前面定义了以下目标：`gtest`、`gtest_main`、`gmock`和`gmock_main`。这个配置中，作为单元测试示例的库依赖项，我们只对`gtest_main`目标感兴趣：

```
target_link_libraries(cpp_test
  PRIVATE
    sum_integers
    gtest_main
)
```

构建代码时，可以看到如何正确地对Google Test进行配置和构建。有时，我们希望升级到更新的Google Test版本，这时需要更改的唯一一行就是详细说明`GIT_TAG`的那一行。

## 更多信息

了解了`FetchContent`及其构建时的近亲`ExternalProject_Add`，我们将在第8章中重新讨论这些命令。有关可用选项的详细讨论，可参考<https://cmake.org/cmake/help/v3.11/module/FetchContent.html> 。

本示例中，我们在配置时获取源代码，也可以将它们安装在系统环境中，并使用`FindGTest`模块来检测库和头文件(<https://cmake.org/cmake/help/v3.5/module/FindTest.html> )。从3.9版开始，CMake还提供了一个Google Test模块(<https://cmake.org/cmake/help/v3.9/module/GoogleTest.html> )，它提供了一个`gtest_add_tests`函数。通过搜索Google Test宏的源代码，可以使用此函数自动添加测试。

当然，Google Test有许多有趣的的特性，可在 <https://github.com/google/googletest> 查看。
