如何使用 CMake 对库的私有特性 (TDD) 进行单元测试
How to unit test private features of library (TDD) with CMake
我想使用 CMake 设置我的项目,以便可以使用 TDD 进行开发,这意味着 包括和测试内部 headers。但是使用 CMake 正确设置库会在我的单元测试中隐藏这些实现细节(对于外部使用也是如此)。
给定如下文件和文件夹结构:
Foo
|-- include
| `-- Foo
| `-- Foo.h
|-- CmakeLists.txt
|-- src
| |-- Bar
| | |-- Bar.h
| | `-- Bar.cpp
| |-- Baz
| | |-- Baz.h
| | `-- Baz.cpp
| |-- Foo.cpp
| `-- CMakeLists.txt
`-- test
|-- Bar
| `-- BarTest.cpp
|-- Baz
| `-- BazTest.cpp
|-- FooTest.cpp
`-- CMakeLists.txt
Foo/CMakeLists.txt
project(Foo)
include(CTest)
add_subdirectory(src)
if(BUILD_TESTING)
add_subdirectory(test)
endif()
Foo/src/CMakeLists.txt
add_library(Foo)
target_include_directories(Foo
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_sources(Foo
PRIVATE
Foo.cpp
Bar/Bar.cpp
Baz/Baz.cpp
)
Foo/test/CMakeLists.txt
find_package(Catch2 REQUIRED)
include(Catch)
add_executable(FooTest
FooTest.cpp
Bar/BarTest.cpp # This file can't #include "Bar/Bar.h"
Baz/BazTest.cpp # This file can't #include "Baz/Baz.h"
)
target_link_libraries(FooTest
PRIVATE
Foo
Catch2::Catch2
)
现在的问题是 FooTest link 以其他项目需要 Foo 库作为依赖项时的方式来对抗 Foo。这造成了我无法在 Bar/BarTest.cpp
和 Baz/BazTest.cpp
中 运行 单元测试的问题,其中包括文件 #include "Bar/Bar.h"
和 #include "Baz/Baz.h
。
在我看来,理想情况下,一个解决方案将能够获得 Foo 的内部 shortcut
目标,其中包括 PUBLIC 的所有内容,然后我可以对其进行 运行 测试。但是安装后内部 headers 是私有的,只有 include/Foo/
中的文件是 public.
我见过的解决方案要么是创建许多子目标,要么只包含每次测试所需的内容。但这看起来很麻烦,并且不太适合像柯南这样的模块化设置的包管理器。
其他解决方案是创建一个重复的 Foo 目标,其中所有内容都是 public,但这需要我将所有内容都写两次。对我来说听起来像是一种肮脏的方式。
我想到的最终解决方案是创建一个内部目标 FooInternal,每个 headers 和源设置为 public,然后 FooTest 可以 link 反对。然后将包装库创建为 Foo,它 links 反对 FooInternal 为私有,但将 include/Foo
文件夹设置为 public headers。
但这需要 Foo 成为接口目标,然后将没有任何 libFoo
导出,让我创建自定义逻辑以重命名或以某种方式设置 CMake 以使用 libFooInternal
作为正确的 lib 文件.这听起来又很脏,我不确定这在实践中会如何工作。
是否有任何明显的解决方案我没有注意到,或者有人对此问题有好的解决方案吗?
编辑3:
edit2 的解决方案实际上不适用于包含多个库的构建树,因为它允许目标访问内部链接目标 headers。
Brad King 在 Kitwares CMake gitlab 上提供的最终使用的解决方案是
add_library(foo foo.c)
target_include_directories(foo PUBLIC "$<BUILD_INTERFACE:$<$<BOOL:$<TARGET_PROPERTY:FOO_PRIVATE>>:/path/to/foo/private/include>>")
set_property(TARGET foo PROPERTY FOO_PRIVATE 1)
add_executable(test_foo test_foo.c)
target_link_libraries(test_foo foo)
set_property(TARGET test_foo PROPERTY FOO_PRIVATE 1)
来源:https://gitlab.kitware.com/cmake/cmake/issues/19048
edit2:最终的解决方案非常简单,只是将包含路径设置为
target_include_directories(Foo
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
edit1:在观看视频后,steveire 链接显示我最近做的编程和测试太少,而面向 devops 的工作太多。
我需要能够测试内部 headers。回到关于如何使用给定设置使用 CMake 正确测试它的原始问题。当我找到一个令人信服的解决方案时会更新这个答案。
原文:看了 Superlokkus 对 cpplang slack 的评论和讨论,似乎最好永远不要测试内部 headers。
如果使用 public headers 的测试和最里面的 .cpp 文件之间的抽象数量太大,将其拆分为一个独立的库。
我想使用 CMake 设置我的项目,以便可以使用 TDD 进行开发,这意味着 包括和测试内部 headers。但是使用 CMake 正确设置库会在我的单元测试中隐藏这些实现细节(对于外部使用也是如此)。
给定如下文件和文件夹结构:
Foo
|-- include
| `-- Foo
| `-- Foo.h
|-- CmakeLists.txt
|-- src
| |-- Bar
| | |-- Bar.h
| | `-- Bar.cpp
| |-- Baz
| | |-- Baz.h
| | `-- Baz.cpp
| |-- Foo.cpp
| `-- CMakeLists.txt
`-- test
|-- Bar
| `-- BarTest.cpp
|-- Baz
| `-- BazTest.cpp
|-- FooTest.cpp
`-- CMakeLists.txt
Foo/CMakeLists.txt
project(Foo)
include(CTest)
add_subdirectory(src)
if(BUILD_TESTING)
add_subdirectory(test)
endif()
Foo/src/CMakeLists.txt
add_library(Foo)
target_include_directories(Foo
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_sources(Foo
PRIVATE
Foo.cpp
Bar/Bar.cpp
Baz/Baz.cpp
)
Foo/test/CMakeLists.txt
find_package(Catch2 REQUIRED)
include(Catch)
add_executable(FooTest
FooTest.cpp
Bar/BarTest.cpp # This file can't #include "Bar/Bar.h"
Baz/BazTest.cpp # This file can't #include "Baz/Baz.h"
)
target_link_libraries(FooTest
PRIVATE
Foo
Catch2::Catch2
)
现在的问题是 FooTest link 以其他项目需要 Foo 库作为依赖项时的方式来对抗 Foo。这造成了我无法在 Bar/BarTest.cpp
和 Baz/BazTest.cpp
中 运行 单元测试的问题,其中包括文件 #include "Bar/Bar.h"
和 #include "Baz/Baz.h
。
在我看来,理想情况下,一个解决方案将能够获得 Foo 的内部 shortcut
目标,其中包括 PUBLIC 的所有内容,然后我可以对其进行 运行 测试。但是安装后内部 headers 是私有的,只有 include/Foo/
中的文件是 public.
我见过的解决方案要么是创建许多子目标,要么只包含每次测试所需的内容。但这看起来很麻烦,并且不太适合像柯南这样的模块化设置的包管理器。
其他解决方案是创建一个重复的 Foo 目标,其中所有内容都是 public,但这需要我将所有内容都写两次。对我来说听起来像是一种肮脏的方式。
我想到的最终解决方案是创建一个内部目标 FooInternal,每个 headers 和源设置为 public,然后 FooTest 可以 link 反对。然后将包装库创建为 Foo,它 links 反对 FooInternal 为私有,但将 include/Foo
文件夹设置为 public headers。
但这需要 Foo 成为接口目标,然后将没有任何 libFoo
导出,让我创建自定义逻辑以重命名或以某种方式设置 CMake 以使用 libFooInternal
作为正确的 lib 文件.这听起来又很脏,我不确定这在实践中会如何工作。
是否有任何明显的解决方案我没有注意到,或者有人对此问题有好的解决方案吗?
编辑3: edit2 的解决方案实际上不适用于包含多个库的构建树,因为它允许目标访问内部链接目标 headers。 Brad King 在 Kitwares CMake gitlab 上提供的最终使用的解决方案是
add_library(foo foo.c)
target_include_directories(foo PUBLIC "$<BUILD_INTERFACE:$<$<BOOL:$<TARGET_PROPERTY:FOO_PRIVATE>>:/path/to/foo/private/include>>")
set_property(TARGET foo PROPERTY FOO_PRIVATE 1)
add_executable(test_foo test_foo.c)
target_link_libraries(test_foo foo)
set_property(TARGET test_foo PROPERTY FOO_PRIVATE 1)
来源:https://gitlab.kitware.com/cmake/cmake/issues/19048
edit2:最终的解决方案非常简单,只是将包含路径设置为
target_include_directories(Foo
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
edit1:在观看视频后,steveire 链接显示我最近做的编程和测试太少,而面向 devops 的工作太多。 我需要能够测试内部 headers。回到关于如何使用给定设置使用 CMake 正确测试它的原始问题。当我找到一个令人信服的解决方案时会更新这个答案。
原文:看了 Superlokkus 对 cpplang slack 的评论和讨论,似乎最好永远不要测试内部 headers。 如果使用 public headers 的测试和最里面的 .cpp 文件之间的抽象数量太大,将其拆分为一个独立的库。