c++cmakegoogletest

How to use multiple GTests in CMake


I have the following project structure:

- include
  - func.h
- src
  - main.cpp
  - func.cpp
- tests
  - CMakeLists.txt
  - func1_test.cpp
  - func2_test.cpp
- CMakeLists.txt

Here is the root CMakeLists.txt:

cmake_minimum_required (VERSION 3.12)

option(BUILD_TESTS "BuildTests" ON)
set(PROJECT_VERSION "0.0.3" CACHE INTERNAL "Version")

project ("test_project" VERSION ${PROJECT_VERSION})
add_executable (test_project "src/main.cpp" "src/func.cpp" "include/func.h")
include_directories("include")

set_property(TARGET test_project PROPERTY CXX_STANDARD 20)

# Enable Hot Reload for MSVC compilers if supported.
if (POLICY CMP0141)
  cmake_policy(SET CMP0141 NEW)
  set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()

set_target_properties(test_project
    PROPERTIES
    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)

# Tests.
if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

And here is tests CMakeLists.txt:

include(FetchContent)
if (POLICY CMP0135)
  cmake_policy(SET CMP0135 NEW)
endif()
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

include(GoogleTest)

set(TestsToRun
  func1_test.cpp
  func2_test.cpp
)
create_test_sourcelist (Tests CppTests.cpp ${TestsToRun})
add_executable (CppTests ${Tests})
target_include_directories(CppTests PRIVATE "include")
set_property(TARGET CppTests PROPERTY CXX_STANDARD 20)
target_link_libraries(CppTests GTest::gtest_main)
set_target_properties(CppTests
    PROPERTIES
    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
foreach (test ${TestsToRun})
    get_filename_component (TName ${test} NAME_WE)
    add_test (NAME ${TName} COMMAND CppTests ${TName})
    #gtest_discover_tests(${test})
endforeach ()

func.h:

#pragma once
int func1();
int func2();

func.cpp:

#include "func.h"

int func1()
{
    return 33;
}

int func2()
{
    return 77;
}

I'm trying to use create_test_sourcelist from CMake docs. But building gives me the following error:

[main] Building folder: /home/user/Code/Test/build/linux-debug 
[build] Starting build
[proc] Executing command: /usr/bin/cmake --build /home/user/Code/Test/build/linux-debug --parallel 22 --
[build] [ 20%] Built target test_project
[build] [ 33%] Built target gtest
[build] [ 46%] Built target gtest_main
[build] [ 60%] Built target gmock
[build] [ 73%] Built target gmock_main
[build] [ 80%] Linking CXX executable ../bin/CppTests
[build] /usr/bin/ld: CMakeFiles/CppTests.dir/CppTests.cpp.o:(.data.rel.ro+0x8): undefined reference to `func1_test(int, char**)'
[build] /usr/bin/ld: CMakeFiles/CppTests.dir/CppTests.cpp.o:(.data.rel.ro+0x18): undefined reference to `func2_test(int, char**)'
[build] /usr/bin/ld: CMakeFiles/CppTests.dir/func1_test.cpp.o: in function `Func1Test_Func1Test_Test::TestBody()':
[build] /home/user/Code/Test/tests/func1_test.cpp:9:(.text+0x28): undefined reference to `func1()'
[build] /usr/bin/ld: CMakeFiles/CppTests.dir/func2_test.cpp.o: in function `Func21Test_Func2Test_Test::TestBody()':
[build] /home/user/Code/Test/tests/func2_test.cpp:9:(.text+0x28): undefined reference to `func2()'
[build] collect2: error: ld returned 1 exit status
[build] gmake[2]: *** [tests/CMakeFiles/CppTests.dir/build.make:131: bin/CppTests] Error 1
[build] gmake[1]: *** [CMakeFiles/Makefile2:180: tests/CMakeFiles/CppTests.dir/all] Error 2
[build] gmake: *** [Makefile:146: all] Error 2
[proc] The command: /usr/bin/cmake --build /home/user/Code/Test/build/linux-debug --parallel 22 -- exited with code: 2
[driver] Build completed: 00:00:00.352
[build] Build finished with exit code 2

Note, I'm commented the gtest_discover_tests call because I dont understand should I use it and what var should I use for it. Here is func1_test.cpp (func2_test.cpp looks similar):

#include <sstream>
#include <limits>
#include <gtest/gtest.h>

#include "../include/func.h"

TEST(Func1Test, Func1Test)
{
    ASSERT_EQ(func1(), 33);
}

Solution

  • Main problem is that sources which should be tested are build as part of target executable: test_project! You need a library which you will link to your executable and to your tests.

    Rearrange sources this way:

    - funlib
      - include
          - funlib
              - func.h
      - src
          - func.cpp
      - CMakeLists.txt
    - demo_app
      - main.cpp
      - CMakeLists.txt
    - tests
      - CMakeLists.txt
      - func1_test.cpp
      - func2_test.cpp
    - CMakeLists.txt
    

    Where:

    CMakelists.txt

    cmake_minimum_required (VERSION 3.12)
    
    option(BUILD_TESTS "BuildTests" ON)
    set(PROJECT_VERSION "0.0.3" CACHE INTERNAL "Version")
    project (test_project VERSION ${PROJECT_VERSION})
    
    add_subdirectory(funlib)
    add_subdirectory(demo_app)
    if(BUILD_TESTS)
        add_subdirectory(tests)
    endif()
    

    funlib/CMakeLists.txt

    add_library(funlib # STATIC?
       src/func.cpp
       include/funlib/func.h
    )
    
    target_include_directories(funlib
       PUBLIC include
       PRIVATE src include/funlib)
    

    Note that in this form you will do #include <funlib/func.h> in other targets to import a function.

    demo_app/CMakeLists.txt

    add_executable(demo_app main.cpp)
    target_link_libraries(demo_app PUBLIC funlib)
    

    tests/CMakeLists.txt

    # importing gtest ....
    # I do not like how you do it, but this is off topic of your question
    
    add_executable(CppTests 
        func1_test.cpp
        func2_test.cpp
    )
    
    target_link_libraries(CppTests PUBLIC GTest::gtest_main funlib)
    
    gtest_discover_tests(CppTests)
    

    Note include path is already address in in funlib you should not do it in CppTests.