本篇演示如何在 CMake 的帮助下使用 Google Test 框架实现单元测试。Google Test 不仅仅是一个头文件,更是一个库,包含需要构建和链接的源文件。可以把它们与我们的代码项目放在一起,但为了使代码项目更加轻量级,我们选择在配置时下载一个版本确定的 Google Test,然后构建框架并链接它。这里使用较新的 FetchContent 模块(从 CMake 3.11 开始可用),本篇将讨论该模块在底层是如何工作的,并演示如何用 ExternalProject_Add 对其进行模拟。
注意:本示例在 CMake 3.11 或更高版本中有效,并且已经在 GNU/Linux、macOS 和 Windows 上进行过测试。代码仓库中另有一个支持 CMake 3.5 的版本。
准备工作
main.cpp、sum_integers.cpp 和 sum_integers.hpp 保持不变,只需修改 test.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 编译可执行文件及其相应的测试。
与前两个示例相比,CMakeLists.txt 的开头基本没有变化,而使用 FetchContent 模块要求 CMake 3.11:
# 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)
然后引入一个 if,检查 ENABLE_UNIT_TESTS。默认情况下它为 ON,但可以设置为 OFF,以便在没有网络连接时跳过单元测试的构建:
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()
if 内部包含 FetchContent 模块,声明要获取的内容,并查询其属性:
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
)
FetchContent_GetProperties(googletest)
如果内容还没有获取到,将尝试获取并配置它,这一步会添加几个可供链接的目标。本例中我们对 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 targets: 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()
然后,使用 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
)
最后,使用 enable_testing 和 add_test 命令定义单元测试:
enable_testing()
add_test(
NAME google_test
COMMAND $<TARGET_FILE:cpp_test>
)
endif()
现在配置、构建并测试项目:
$ 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
也可以直接运行 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 模块的功能进行了封装,可以在配置时填充内容,它从 CMake 3.11 起成为 CMake 的标准组成部分。而 ExternalProject_Add() 则在构建时进行下载操作。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 命令的选项一致。
调用 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 之后,后续示例中还会重新讨论这些命令。
本示例在配置时获取源代码;也可以把 Google Test 安装到系统环境中,再使用 FindGTest 模块来检测库和头文件。从 3.9 版开始,CMake 还提供了一个 GoogleTest 模块,它提供了 gtest_add_tests 函数,通过搜索源代码中的 Google Test 宏,可以自动添加测试。
当然,Google Test 还有许多有趣的特性,这里不再一一展开。