CMake构建单元测试时链接静态库出现未定义引用错误
C++项目GoogleTest单元测试链接错误与优化方案
问题场景
项目文件夹结构:
*--build |---(building cmake here) | *--main.cpp | *--CMakeLists.txt (root) | *--modules |---foo *------foo.h |------foo.cpp |------CMakeLists.txt *------test |--------main.cpp |--------CMakeLists.txt
根目录CMakeLists.txt:
cmake_minimum_required(VERSION 3.15.2) project(foobar) add_executable(${PROJECT_NAME} main.cpp) add_library(foolib STATIC) # Build sub-modules include_directories(modules/foo) add_subdirectory(modules/foo) target_link_libraries(${PROJECT_NAME} PRIVATE foolib) target_link_libraries(foo_test PRIVATE gtest_main foolib)
foo模块CMakeLists.txt:
file(GLOB FOO_SRC *.h *.cpp) target_include_directories(foolib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # add sources to the library target_sources(foolib PUBLIC ${FOO_SRC}) # bootstrap gtest if(BUILD_TESTS) enable_testing() add_subdirectory(test) endif()
测试目录CMakeLists.txt:
file(GLOB TEST_SRCS *.h *.cpp) target_include_directories(foolib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # add sources to the library target_sources(foolib PUBLIC ${FOO_SRC}) include_directories(googletest) include_directories( ${CMAKE_SOURCE_DIR}/modules/foo ) add_executable(foo_test ${TEST_SRCS}) gtest_discover_tests(foo_test)
测试代码main.cpp:
#include <iostream> #include <string> #include "../foo.h" TEST(builder_tests, initial_test) { std::string s = "please work"; Foobar fb(s); EXPECT_EQ(fb.test_string, "please work"); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
构建测试时出现链接错误:
Linking CXX executable FOO_TEST /usr/bin/ld: CMakeFiles/FOO_TEST.dir/main.cpp.o: in function `foo_tests_initial_test_Test::TestBody()': /inference/modules/foo/test/main.cpp:15: undefined reference to `foo::foo(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)' collect2: error: ld returned 1 exit status make[3]: *** [modules/foo/test/CMakeFiles/foo_TEST.dir/build.make:234: modules/foo/test/FOO_TEST] Error 1 make[2]: *** [CMakeFiles/Makefile2:842: modules/foo/test/CMakeFiles/foo_TEST.dir/all] Error 2 make[1]: *** [CMakeFiles/Makefile2:849: modules/foo/test/CMakeFiles/foo_TEST.dir/rule] Error 2 make: *** [Makefile:383: FOO_TEST] Error 2
错误原因分析
- 测试目录CMake配置错误:
- 重复给
foolib添加源文件,但FOO_SRC变量仅在foo模块CMake中定义,测试目录未声明,导致foolib未包含foo.cpp实现,链接时找不到构造函数。 - 仅通过
include_directories添加GoogleTest路径,但未实际链接库或拉取依赖源码。
- 重复给
- 根目录CMake顺序问题:
- 提前调用
target_link_libraries(foo_test ...),但foo_test是在BUILD_TESTS条件下才创建,此时目标尚未定义,引发配置异常。
- 提前调用
file(GLOB)的缺陷:- 依赖自动扫描源文件,新增文件不会触发CMake重新配置,可能导致源文件漏加。
修复步骤
1. 修正测试目录CMakeLists.txt
移除对foolib的重复配置,正确引入并链接GoogleTest:
file(GLOB TEST_SRCS *.h *.cpp) add_executable(foo_test ${TEST_SRCS}) # 自动拉取GoogleTest依赖 include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip ) set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) # 链接测试依赖库 target_link_libraries(foo_test PRIVATE gtest_main foolib) gtest_discover_tests(foo_test)
2. 修正根目录CMakeLists.txt
添加测试开关,移除提前引用foo_test的逻辑:
cmake_minimum_required(VERSION 3.15.2) project(foobar) option(BUILD_TESTS "Build unit tests" ON) add_executable(${PROJECT_NAME} main.cpp) add_library(foolib STATIC) add_subdirectory(modules/foo) target_link_libraries(${PROJECT_NAME} PRIVATE foolib)
3. 修正foo模块CMakeLists.txt
显式列出源文件,替代file(GLOB):
set(FOO_SRC foo.h foo.cpp ) target_include_directories(foolib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(foolib PUBLIC ${FOO_SRC}) if(BUILD_TESTS) enable_testing() add_subdirectory(test) endif()
4. 修正测试代码的头文件引用
利用foolib的PUBLIC包含路径,直接引用头文件:
#include <iostream> #include <string> #include "foo.h" TEST(builder_tests, initial_test) { std::string s = "please work"; Foobar fb(s); EXPECT_EQ(fb.test_string, "please work"); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
项目结构与CMake优化建议
- 目录结构优化:
- 将测试文件重命名为
foo_test.cpp,明确对应模块;若后续新增多模块测试,可在根目录新增tests目录统一管理。
- 将测试文件重命名为
- CMake最佳实践:
- 完全替代
include_directories,使用target_include_directories实现依赖隔离。 - 永远显式列出源文件,避免
file(GLOB)导致的文件遗漏问题。 - 用
FetchContent管理第三方依赖,保证开发环境一致性。 - 测试目标的链接逻辑放在对应测试目录的CMake中,避免跨目录提前引用未定义目标。
- 完全替代
- 测试代码优化:
- 避免直接访问类私有成员,提供公共接口获取值,符合封装原则。
- 拆分测试用例,每个用例只验证单一场景。
内容的提问来源于stack exchange,提问作者Grudy
相关产品推荐
相关产品推荐

