mardi 12 février 2019

How to unit test private features of library (TDD) with CMake

I want to set up my project with CMake such that it can be developed using TDD, which means 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
    Baz/BazTest.cpp
)

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?

Aucun commentaire:

Enregistrer un commentaire