c++cmakegoogletestctest

Issue integrating gtest in a cmake project


I have the following folder structure for my cmake project. My app is dependent on curl. I also have a tests folder in which I perform some testing on the curl library. I have access to the curl header files in myapp but my IDE complains about #include <curl/curl.h not found in the source code curl_gtests.cpp.

I read online that the following folder structure is a good structure especially since we have a separate tests folder that utilizes the built libraries and executables and it makes it clean and organized. However, I have difficulty creating access to the tests folder to those libraries/executables that are external or developed within myapp

myapp
│   CMakeLists.txt
│
├───data
│       data.csv
│
├───external
│   └───curl
│           CMakeLists.txt
│
├───include
│       myapp.h
│
├───out
├───src
│       main.cpp
│
└───tests
        CMakeLists.txt
        curl_gtests.cpp
    

The content of the top level CMakeLists.txt is as follows:

cmake_minimum_required(VERSION 3.24)
project(myapp VERSION 1.0.0)

include(FetchContent)
include(CMakePrintHelpers)

cmake_print_variables(CMAKE_CXX_FLAGS)

set(CMAKE_CXX_STANDARD 20)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

# Enable testing
enable_testing()

add_subdirectory(external/curl)
add_subdirectory(tests)

add_executable(myapp)
target_sources(myapp PRIVATE src/main.cpp)
target_include_directories(myapp PUBLIC include)
target_link_libraries(trade-analytica-app PUBLIC CURL::mylibcurl)

The content of CMakeLists.txt for the curl library is as follows:

cmake_minimum_required(VERSION 3.24)
project(libcurl LANGUAGES CXX VERSION 1.0.0)

if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
    set(WIN32 ON)
    set(MACOS OFF)
    set(LINUX OFF)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
    set(WIN32 OFF)
    set(MACOS ON)
    set(LINUX OFF)
else()
    set(WIN32 OFF)
    set(MACOS OFF)
    set(LINUX ON)
endif()

include(FetchContent)
include(CMakePrintHelpers)

if (WIN32)
    set(CMAKE_USE_SCHANNEL ON)
endif()

FetchContent_Declare(curl
        URL                    https://github.com/curl/curl/releases/download/curl-7_75_0/curl-7.75.0.tar.xz
        URL_HASH               SHA256=fe0c49d8468249000bda75bcfdf9e30ff7e9a86d35f1a21f428d79c389d55675 # the file hash for curl-7.75.0.tar.xz
        USES_TERMINAL_DOWNLOAD TRUE)

FetchContent_MakeAvailable(curl)


add_library(curl_int INTERFACE)
target_include_directories(curl_int INTERFACE
        $<BUILD_INTERFACE:${curl_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include/curl>  # <prefix>/include/mylib
        )
target_link_libraries(curl_int INTERFACE libcurl)

add_library(CURL::mylibcurl ALIAS curl_int)
get_target_property(curl_int_dir CURL::mylibcurl INTERFACE_INCLUDE_DIRECTORIES)

and finally for the CMakeLists.txt in the tests folder:

cmake_minimum_required(VERSION 3.24)

if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
    set(WIN32 ON)
    set(MACOS OFF)
    set(LINUX OFF)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
    set(WIN32 OFF)
    set(MACOS ON)
    set(LINUX OFF)
else()
    set(WIN32 OFF)
    set(MACOS OFF)
    set(LINUX ON)
endif()

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)

if(WIN32)
    set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
endif()

FetchContent_MakeAvailable(googletest)

add_executable(
  hello_test
  curl_gtests.cpp
)
target_link_libraries(
  hello_test
  GTest::gtest_main
)

include(GoogleTest)
gtest_discover_tests(hello_test)

Solution

  • I read online that the following folder structure is a good structure (...)

    Your project layout tree loosely resembles the The Pitchfork Layout (PFL) specification.

    Regarding your problem, if you want to cover components of your project with unit tests, you need to break your projects into components that can be consumed by the unit test targets. By the looks of your project, you haven't done yet.

    Here's the relevant section from your ./CMakeLists.txt:

    (...)
    
    # Enable testing
    enable_testing()
    
    add_subdirectory(external/curl)
    add_subdirectory(tests)
    
    add_executable(myapp)
    
    (...)
    

    It looks like your project does not specify any library target, and only specifies an executable target: myapp.

    This means you provided CMake with no good way to make your code available to testing, because it has no build artifact (library, module) that makes any component available as a target for other targets such as your unit tests.

    Your unit test targets also do not list any other target as a dependency. Here's the contents of your ./tests/CMakeLists.txt:

    (...)
    
    add_executable(
      hello_test
      curl_gtests.cpp
    )
    target_link_libraries(
      hello_test
      GTest::gtest_main
    )
    
    (...)
    

    In the call above, you create the CMake target hello_test and then set GTest::gtest_main as one of its dependencies, but it does not include anything else. If you want your tests to access a component in your code, you need to include its CMake target in target_link_libraries along with GTest::gtest_main.

    What you need to do is to peel out the bits you want to cover with tests into an independent component (i.e., a library, and in your case either a static or module library), and then pass its CMake target as a dependency of the unit test target.