I want to set up my project with CMake such that it can be developed using TDD, which means including and testing internal headers . But setting up the library correctly with CMake hides these implementation details (and correctly so for external use) from my unit tests.
Given a file and folder structure like this:
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
)
The problem now is that FooTest links against Foo in a way that other projects do when they need the Foo library as a dependency. This creates the problem that I can't run unit tests in Bar/BarTest.cpp
and Baz/BazTest.cpp
which includes the files #include "Bar/Bar.h"
and #include "Baz/Baz.h
. Ideally in my mind a solution would be able to get an internal shortcut
target to Foo which includes everything as PUBLIC which then I can run tests on. But when installed the internal headers are private and only files in include/Foo/
is public.
Solutions I've seen is either to create many sub targets and include just what you need for each test. But this seems cumbersome and fits not very good with package managers such as Conan in a very modular setup.
Other solution is to create a duplicate Foo target where everything is public, but this requires me to write everything twice. Sounds like a dirty way to me.
A final solution I thought about is to create an internal target FooInternal with every headers and sources set to public which then FooTest can link against. And then create the wrapping library as Foo which links against FooInternal as private, but sets the include/Foo
folder as public headers. But this requires either Foo to be an interface target, which then will not have any libFoo
to export, having me to create custom logic to rename or somehow setup CMake to use the libFooInternal
as the correct lib file. Again this sounds dirty and I'm not sure how this would work in practice.
Are there any obvious solutions I am being oblivious about, or does anyone have a good solution to this problem?
edit3: Solution from edit2 will actually not work well with build trees that contains multiple libraries, because it gives targets access to linked targets internal headers. Solution offered by Brad King on Kitwares CMake gitlab that was finally used is
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)
source: https://gitlab.kitware.com/cmake/cmake/issues/19048
edit2: The final solution which ended up being ridiculously simple was just to set the include paths to
target_include_directories(Foo
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
edit1: After looking at the video steveire linked shows that I have done too little programming and testing lately and too much devops oriented work. I need to be able to test internal headers. Back to the original problem on how to test it correctly with CMake with the given setup. Will update this answer when I find a compelling solution.
Original: After going by Superlokkus' comment and discussion on the cpplang slack, it seems to be best never to test internal headers. If the number of abstractions between the tests that use the public headers and the innermost .cpp file is too large, split it into a standalone library.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.