【发布时间】:2019-07-06 05:36:28
【问题描述】:
我想使用 CMake 设置我的项目,以便可以使用 TDD 进行开发,这意味着包括和测试内部标头。但是使用 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 链接到 Foo 的方式与其他项目在需要 Foo 库作为依赖项时所做的一样。这造成了我无法在Bar/BarTest.cpp 和Baz/BazTest.cpp 中运行单元测试的问题,其中包括文件#include "Bar/Bar.h" 和#include "Baz/Baz.h。
理想情况下,在我看来,一个解决方案将能够获得 Foo 的内部 shortcut 目标,其中包括所有作为 PUBLIC 的内容,然后我可以在其上运行测试。但是安装后内部标头是私有的,只有include/Foo/ 中的文件是公开的。
我见过的解决方案是创建许多子目标并包含每个测试所需的内容。但这似乎很麻烦,并且不太适合在非常模块化的设置中使用包管理器,例如柯南。
其他解决方案是创建一个重复的 Foo 目标,其中所有内容都是公开的,但这需要我将所有内容写两次。对我来说听起来很肮脏。
我想到的最终解决方案是创建一个内部目标 FooInternal,其中每个标头和源都设置为公共,然后 FooTest 可以链接到该目标。然后将包装库创建为 Foo,它将 FooInternal 链接为私有,但将 include/Foo 文件夹设置为公共标头。
但这需要 Foo 成为接口目标,然后将没有任何 libFoo 导出,让我创建自定义逻辑以重命名或以某种方式设置 CMake 以使用 libFooInternal 作为正确的 lib 文件。同样,这听起来很脏,我不确定这在实践中会如何工作。
有没有我忽略的明显解决方案,或者有没有人对此问题有好的解决方案?
【问题讨论】:
-
为什么不能运行单元测试?我不明白你在这里的意思,除了你似乎有一个设计问题。
-
我将我的主要问题加粗,在我的文字墙中可能很难看到。我想测试内部头文件,那些没有放在 Foo/include/Foo 文件夹中的头文件。
-
所以您还需要将
src文件夹添加到您的包含路径中以进行测试吗?它是 TDD,因此具有侵入性,您不能使用“标准”访问。您需要添加src。 -
我的建议是,即使使用 TDD,您也应该测试库的面向公众的界面。如果您的内部库的内部结构过于复杂,则将其移至另一个库,您可以对其进行测试,并且其公共接口将成为使用者私有实现的一部分。大型单体库是不好的做法。但是,可以在一个库中汇总一堆它们以使用 100 个非常小的库。
-
@Superlokkus :总的来说,我同意。如果单元测试过于关心实现细节,它们就会变得脆弱。如果 Foo.cpp 包含 Bar.h 和 Baz.h,那只是包含 Foo.h 的 FooTest.cpp 和实际(间接)测试 Bar/Baz.cpp 之间的一种抽象。这可能没问题。但是,它到达两个抽象层的点是做 TDD 变得困难的点,还是应该将其拉出到自己的独立库的点?作为第二点,对于转换旧项目,是否有任何平滑的方法来测试内部标头?
标签: c++ unit-testing testing cmake tdd