Integrate a unit test framework in cmake

Last Modify: 13 May, 2018 | Create: 13 May, 2016 | 3 min read

Automatic tests are ubiquitous in software industry these days. Especially to large-scale software, it is necessary to have a set of test to ensure the specification is fulfilled and to prevent regression bugs. In this post, I will not bore you with why we do test. Instead, if you are using the CMake build system, I will show you how easy it is to integrate test into the system.

In this tutorial, my directory structure includes two parts: the sources (in src subfolder) and tests (in test subfolder).

The test framework I chose is Catch2. However, the process of using different frameworks like CppUnit, Boost Test Library, doctest, or googletest should be very similar.

Setup

Firstly, I added all the source code except main.cpp into a library called common and linked it by both the production program and the test.

./CMakeLists.txt

cmake_minimum_required (VERSION 2.8) 
project (MyAwesomeProject) 

...

add_subdirectory (src)

...

add_executable(MyAwesomeProject src/main.cpp)
target_link_libraries (MyAwesomeProject common)

add_subdirectory (test)

src/CMakeLists.txt

add_library (common a.hpp a.cpp b.hpp)

Configure test framework

We can create a test/CMakeLists.txt that deals with testing stuff. You can then put the unit test library into your repository. Since Catch is a header-only library, we can use CMake's interface library to handle it. For libraries like googletest, just link it as a normal library.

# Add catch as an interface library
set(CATCH_INCLUDE_DIR <WHERE YOUR Catch.hpp is>)
add_library(Catch INTERFACE)
target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR})

# Add test executable
add_executable (tests testmain.cpp testA.cpp testB.cpp)
target_link_libraries(tests Catch CommonSourceCode)

An alternative: CMake external project

An (unrecommended) alternative is to fetch the test framework from GitHub automatically and configured it as a CMake external project. This way you do not need to worry about updating the test framework to its latest version. Do aware this way you cannot compile the code without Internet connection since whenever CMake runs it will try to fetch your unit test framework online.

include(ExternalProject)
find_package(Git REQUIRED)

ExternalProject_Add(
    catch
    PREFIX ${CMAKE_BINARY_DIR}/test/catch
    GIT_REPOSITORY https://github.com/philsquared/Catch.git
    TIMEOUT 10
    UPDATE_COMMAND ${GIT_EXECUTABLE} pull
    CONFIGURE_COMMAND ""
    BUILD_COMMAND ""
    INSTALL_COMMAND ""
    LOG_DOWNLOAD ON
   )

# Expose required variable (CATCH_INCLUDE_DIR) to parent scope
ExternalProject_Get_Property(catch source_dir)
set(CATCH_INCLUDE_DIR ${source_dir}/include CACHE INTERNAL "Path to include folder for Catch")

# Add catch as an interface library
add_library(Catch INTERFACE)
target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR})

# Add test executable
add_executable (tests testmain.cpp testA.cpp testB.cpp)
target_link_libraries(tests Catch CommonSourceCode)

ctest support

Now we can run the test program manually by executing the test executable. We can even configure our IDE to run the test excutable whenever we compile.

Still, we can do better. CTest is the test driver program that CMake provides. To enable ctest, we need CMake to realize our tests executable is for tests.

add_test(NAME tests COMMAND tests)
enable_testing()

To enable CTest, add the following line into the top level CMakeLists file after we define the project.

include(CTest)

Package managers

If your project use package managers like Conan or hunter, integrating unit test frameworks should be brainless. However, the idea of making a test executable alongside the main one still apply.

Conclusion

CMake is a widely accepted cross-platform build tool across the industry. Adding unit test and other supporting tools like CI and static analysers to it is incredibly easy.

If you are still using IDE specific configurations or good old Makefiles for C or C++ projects, I will suggest you spend several hours learning how to use CMake. It will be a production boost.