【问题标题】:How to unit test private features of library (TDD) with CMake如何使用 CMake 对库 (TDD) 的私有功能进行单元测试
【发布时间】: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.cppBaz/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


【解决方案1】:

编辑3: edit2 的解决方案实际上不适用于包含多个库的构建树,因为它使目标可以访问链接目标的内部标头。 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 的工作太多。 我需要能够测试内部标头。回到关于如何使用给定设置使用 CMake 正确测试它的原始问题。当我找到令人信服的解决方案时会更新此答案。


原文:在通过 Superlokkus 对 cpplang slack 的评论和讨论之后,似乎最好不要测试内部标头。 如果使用公共标头的测试与最里面的 .cpp 文件之间的抽象数量太大,请将其拆分为独立库。

【讨论】:

猜你喜欢
  • 2018-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-20
  • 1970-01-01
  • 2017-09-11
相关资源
最近更新 更多