这篇文章记录一下我现在在 C++ 中使用的测试框架和测试流程(可比 Go, Rust 复杂多了…). 我用 GoogleTest 做单元测试, 用 CMake 构建和运行单元测试, 用 Clang 和 llvm-cov 做覆盖率的报告.
CMake 测试模块
CMake 有一个测试模块, 叫做 CTest. 在根 CMakeLists.txt 用以下代码可以把这个模块加入进来.
1 2 3
| if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR MYPROJECT_BUILD_TESTING) include(CTest) endif()
|
有时候我们把当前代码作为其他代码库的依赖库. 这个情况下, 不需要测试. 所以我们用 CMAKE_PROJECT_NAME 和 PROJECT_NAME 来判断当前代码是否是一个独立的项目. 如果是, 才加入 CTest.
同样, 我们需要加入测试代码所在的文件夹.
1 2 3 4 5 6 7
| if( BUILD_TESTING AND (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR MYPROJECT_BUILD_TESTING) ) add_subdirectory(tests) endif()
|
这里需要开一个后门. 也许最后还是需要运行子项目的测试. 这个时候就用 MYPROJECT_BUILD_TESTING 来判断是否需要测试. 另外, 上面加入 CTest 的时候没有加入 BUILD_TESTING, 是因为 CTest 中已经包含了这个功能.
使用 CTest 可以让你在进入构建好的测试程序之后, 输入 ctest 命令就完成测试.
加入 GoogleTest
首先需要加入 GoogleTest 这个库. 推荐使用两种方法引入它: git submodule 或者 CMake FetchContent.
用 git submodule :
1 2
| git submodule add git@github.com:google/googletest.git ./third_party/googletest
|
用 FetchContent:
1 2 3 4 5 6 7 8
| include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip )
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest)
|
另外就是, 不要忘了用 target_include_directories 或者 include_directories() (好像前者更推荐使用一些.) 把 GoogleTest 纳入头文件查找目录.
编写单元测试
这部分不是我的重点, 所以只是略加介绍. 一个最简单的单元测试长这样.
1 2 3 4 5 6
| #include "gtest/gtest.h" #include <iostream>
TEST(Suite1, Test1) { EXPECT_EQ(1, 1); }
|
不需要写主函数. 没主函数, 链接上 gtest_main 就会白送一个.
构建测试可执行文件
使用 CMake 构建可执行文件:
1 2
| add_executable(${测试的名字} EXCLUDE_FROM_ALL ${测试源代码}) target_link_libraries(${测试的名字} gtest gtest_main)
|
之后, 需要利用 GoogleTest 提供的指令, 把测试加入到 CTest 中. 这个指令是:
1 2 3 4 5 6 7 8 9 10 11 12 13
| gtest_discover_tests(target [EXTRA_ARGS arg1...] [WORKING_DIRECTORY dir] [TEST_PREFIX prefix] [TEST_SUFFIX suffix] [TEST_FILTER expr] [NO_PRETTY_TYPES] [NO_PRETTY_VALUES] [PROPERTIES name1 value1...] [TEST_LIST var] [DISCOVERY_TIMEOUT seconds] [XML_OUTPUT_DIR dir] [DISCOVERY_MODE <POST_BUILD|PRE_TEST>] )
|
然后在测试可执行程序的文件夹下面运行 ctest 就可以测试了.
我一般使用以下的指令自动寻找测试文件, 自动构建测试目标.
1 2 3 4 5 6
| file(GLOB_RECURSE TEST_SOURCES "${PROJECT_SOURCE_DIR}/test/*/*test.cc") message(STATUS "Found ${TEST_SOURCES} test sources: ${PROJECT_SOURCE_DIR}/test/") foreach (test_source ${TEST_SOURCES}) endforeach ()
|
覆盖率
我写了一个 Cmake Module 来做这件事. 用法是:
1 2 3 4
| set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/build_support/cmake) set(TEST_SOURCES_PATTERN ....) include(LCOV) include(tests)
|
它基本上需要和我的 Googletest 用 Cmake 文件一起使用, 我没试过其他的用法.
需要在电脑上有 llvm-cov llvm-profdata. 运行构建指令 coverage-report 之后, 会在 build 文件夹下面生成 report.html 文件. 用浏览器打开就可以看到覆盖率报告了. 具体原理就不详细介绍了. 效果大概是这样:

参考资料
[1] Modern CMake
[2] GoogleTest User’s Guide
[3] Cmake Document
[4] Clang document: Source-based code cooverage
[5] llvm-cov document