During software development, it’s important to be able to test your code, so you can catch those pesky bugs as early as possible in the development process. For C++ software engineers, this means having many unit and functional tests that can tell you how your code behaves in certain situations. Unsurprisingly, Google Test is the most popular testing framework for these purposes, and it can be easily integrated with CMake.
If you haven’t yet come across Google Test, it simply allows you to define test cases with its API. In addition, it creates a very light-weight executable/program with beautiful pass/failure outputs based on your test outcomes. Perfect for CI/CD pipeline integration.
What’s even more interesting? Google Tests can be integrated quite easily into your CMake projects, using CMake’s test tool: CTest.
Contents Of This Post
Getting started with Google Tests in a CMake project is very easy. In this post, we look at three different ways to achieve this:
- Explaining the project setup.
- Using CMake’s
fetch_content
to pull GTest’s code. - Pulling GTest as a third-party dependency with Conan.
- Pulling GTest as a third-party dependency with Vcpkg.
- Getting the code from Github.
- My thoughts on different methods of integrating GoogleTest.
Hopefully, one of the options above will persuade you to use Google Tests in your projects. Believe it or not, I’ve seen many projects (commercial and open-source) that don’t, and they end up wasting a lot of time writing their own flawed testing frameworks!
Fitting Google Test Into A CMake Typical Project – The Big Picture
Firstly, let’s look at an example of a typical C++ project. For example, say your project defines a library with some common code and an executable that uses the functions inside the library. Unsurprisingly, we will look at a very similar setup in this post.
Specifically, we consider a library that defines three different functions. Particularly, we create a target called multiply
, which defined three functions to multiply two numbers. Essentially, a binding for each type {int, float, double}
.
File Structure Of The Test Project
In the textbox below, you can find the directory structure for the basic example we’re considering in this post.
.
├── CMakeLists.txt
├── src
│ ├── CMakeLists.txt
│ └── multiply
│ ├── CMakeLists.txt
│ ├── include
│ │ └── multiply
│ │ └── multiply.h
│ └── multiply.cpp
└── tests
├── CMakeLists.txt
└── multiply_test.cpp
Moreover, you can find the contents of important files in the following subsections. However, we basically have all the code for the library under src/multiply
, and the test executable under tests
. Additionally, each directory has a CMakeLists.txt, either to include the subdirectories or to define targets in the current directory.
Main CMakeLists.txt – Defining The Project And Adding Subdirectories
As you can see below, the main CMakeLists.txt
file declares the project properties and CMake’s boilerplate code.
cmake_minimum_required(VERSION 3.16)
project(MultiplyTest LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
enable_testing()
add_subdirectory(src)
add_subdirectory(tests)
Clearly, we’re setting the minimum version of CMake, declaring the project name, and immediately settings the C++ standard version boilerplate code. Interestingly, we need the call to enable_testing()
as it tells CMake to generate the tests that we will later on create. Remember to add enable_testing()
in your root CMakeLists.txt
, before any tests are created.
Lastly, we add the subdirectories for the library source code and the test code.
Creating A Dummy CMake Library For Our Google Tests
Under the directory src
, we define the code and CMake targets for our dummy library multiply
. Specifically, the interesting bits will be under the multiply
directory, so the file src/CMakeLists.txt
only has a call to add_subdirectory(multiply)
in it.
For this reason, the filesrc/multiply/CMakeLists.txt
defines our library multiply
, and the include directories for it, as you can see below.
add_library(multiply multiply.cpp)
target_include_directories(multiply PUBLIC include)
With the above CMake structure in mind for our dummy target, the source file multiply.cpp
has the definitions of the functions for our multiply
library.
#include <multiply/multiply.h>
int multiply(int a, int b)
{
return a*b;
}
float multiply(float a, float b)
{
return a*b;
}
double multiply(double a, double b)
{
return a*b;
}
Complementary, you can find the contents of the header file src/multiply/include/multiply/multiply.h
below.
int multiply(int a, int b);
float multiply(float a, float b);
double multiply(double a, double b);
Defining The Google Tests
Having created our dummy library with a few public functions, we now need to test it. Luckily for you, I created an executable with a few unit tests. Check the content of the relevant C++ source file below.
#include <multiply/multiply.h>
#include <gtest/gtest.h>
TEST(MultiplyTests, TestIntegerOne_One)
{
const auto expected = 1;
const auto actual = multiply(1, 1);
ASSERT_EQ(expected, actual);
}
TEST(MultiplyTests, TestIntegerZero_Zero)
{
const auto expected = 0;
const auto actual = multiply(0, 0);
ASSERT_EQ(expected, actual);
}
TEST(MultiplyTests, TestIntegerZero_One)
{
const auto expected = 0;
const auto actual = multiply(0, 1);
ASSERT_EQ(actual, expected);
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Finally, to complete the setup, we define the CMake test targets in the file tests/CMakeLists.txt
below.
# GTest include code would
# go somewhere in here
add_executable(multiply_test multiply_test.cpp)
target_link_libraries(multiply_test
PRIVATE
GTest::GTest
multiply)
add_test(multiply_gtests multiply_test)
However, not that I left a comment block at the top of the CMake file above. Due to having different ways of integrating Google Tests into your project, this CMakeLists.txt
will change. Hopefully, you should quickly see this in the upcoming sections about the actual integration!
Learn More About The CMake Techniques Used So Far
Before we move on to the next sections, I want to make sure you’re not stuck or wondering what we’ve just done!
Specifically, we made heavy use of CMake to define a C++ project, libraries, and executables. For more information, check out the post on how to use CMake’s add_library(...)
for creating libraries, and also how to use CMake’s include_directories(...)
to expose public functions/API for a library!
Integrating Google Tests With CMake’s fetch_content(...)
Unsurprisingly, there’s a way to include third-party libraries purely with CMake. Using the module “FetchContent”, we can use the functions FetchContent_Declare(...)
and FetchContent_MakeAvailable(...)
that pretty much copies or downloads a different CMake project and makes it part of your build.
Without further ado, the code block below is the modified tests/CMakeLists.txt
that pulls version 1.11.0
of GTest, then links it to our multiply_test
executable.
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.11.0
)
FetchContent_MakeAvailable(googletest)
add_library(GTest::GTest INTERFACE IMPORTED)
target_link_libraries(GTest::GTest INTERFACE gtest_main)
add_executable(multiply_test multiply_test.cpp)
target_link_libraries(multiply_test
PRIVATE
GTest::GTest
multiply)
add_test(multiply_gtests multiply_test)
Long story short, the FetchContent_*
calls above will pull the code from Google Tests’ git repository, then include it as part of your build, hence all the targets defined in Google Tests’ CMake will also be available in our build.
In addition, I added the calls to add_library(GTest::GTest INTERFACE IMPORTED)
and target_link_libraries(GTest::GTest INTERFACE gtest_main)
to make the “linking” of GTests more consistent with the other methods.
Building The Tests With The FetchContent Method
Due to the FetchContent
method being a pure CMake way of integrating third-party libraries, this is perhaps the easiest one to build. For this reason, the short code block below is enough to build the entire thing.
# Assuming you're executing these commands
# from the root directory!
mkdir build && cd build
cmake ..
cmake --build .
If all goes well, you should have the test executable under build/tests/multiply_tests
, and the library files under the directorybuild/src/multiply/
.
Furthermore, since we added a CMake test with add_test(...)
in tests/CMakeLists.txt
, you should be able to run all defined tests by executing the command ctest
under the build/
directory.
My Thoughts And Further Information On FetchContent
For simple projects with few dependencies, FetchContent
is an acceptable way to integrate third-party dependencies defined with CMake. Thankfully, the CMake documentation on FetchContent
isn’t too bad, so you can read more about it there.
Although CMake’s FetchContent
does handle non-CMake projects, it’s way harder to include these third-party dependencies. More precisely, you would find yourself writing a lot of extra CMake to CMake-fy your third-party dependency, essentially maintaining it for your build system. We won’t get into how to do that in this post.
In terms of versioning, FetchContent
actually pulls Google Tests with the version given in the GIT_TAG
parameter. I would stick to git tags or commit hashes, but it also supports branch names.
For a comparison between FetchContent
and the other methods, refer to the summary section of this post!
Conan To Manage Google Tests As A Third-Party Dependency
Conan is a package manager for C++. In simple terms, this means that Conan is a tool that helps you include third-party packages into your build. Unfortunately, we will not explain how Conan works in depth, however, the list below outlines the main steps for including Google Tests with Conan.
- Add all the third-party dependencies of your project inside the file
conanfile.txt
. - Install the dependencies with Conan, preferably inside your
build
directory. - Modify your CMake to include the dependencies downloaded with Conan.
- Build your project.
Understandably, doing all of this may seem like a lot. However, it all boils down to creating a text file and adding a few lines to the already existing tests/CMakeLists.txt
.
And of course, make sure you install Conan by following the official guide!
Declaring And Downloading Dependencies With Conan
As previously mentioned, we need to create the file conanfile.txt
with the dependencies for our C++ build. In our case, we simply need a dependency on Google Tests version 1.11.0
, as you can see below.
[requires]
gtest/1.11.0
[generators]
cmake_find_package
Basically, the conanfile.txt
above says we want to use the gtests/1.11.0
package in our project, and we want Conan to generate CMake find packages, so we can use it with find_package(...)
in CMake. For more information on the conanfile.txt
, check out Conan’s documentation. To find out which libraries and versions are available, check out the Conan center website.
Following the creation of the file above in your project’s root directory, you need to ask Conan to install the dependencies in your machine with the following commands.
mkdir -p build && cd build
conan install .. \
-scompiler=gcc \
-scompiler.version=9 \
-scompiler.libcxx=libstdc++11 \
--build=missing
Expectedly, the commands above download all the source code for the dependencies and will build them. Furthermore, note that I defined a few settings for my build, such as the compiler I’m using, the version of the compiler, and the C++ standard library implementation to use libstdc++11
. Feel free to match your compiler name and version, but you must use that C++ standard library for Google Tests to compile smoothly.
After these steps, you will find a FindGTest.cmake
file under the build/
directory. Hence, it means that everything worked well.
Modifying The CMake And Building The GTest Executable
Luckily, we only need to make one minor modification to the file tests/CMakeLists.txt
, as you can see below.
find_package(GTest REQUIRED)
add_executable(multiply_test multiply_test.cpp)
target_link_libraries(multiply_test
PRIVATE
GTest::GTest
multiply)
Essentially, we added the call to find_pacakge(GTest REQUIRED)
. Long story short, this will use the FindGTest.cmake
file to define the target GTest::GTest
. Finally, the code block below shows the commands to build the project from the command line.
cd build
cmake -DCMAKE_MODULE_PATH="$(pwd)" ..
cmake --build .
And that’s it, you should have compiled everything, and you can run all the tests with ctest
under the build directory.
Before we finish this section, I want to point out that the option -DCMAKE_MODULE_PATH="$(pwd)"
will essentially tell CMake where it can find the FingGTest.cmake
file. That’s why the find_package(GTest REQUIRED)
command finds GTest!
Vcpkg As Another Third-Party Way Of Getting Google Tests Into Your Project
Vcpkg is also a C++ package manager. Similarly, we also need to declare the dependencies and install them with Vcpkg, before building with CMake. You can find the summary of the steps needed to include Google Tests with Vcpkg in the list below.
- Declare the Google Test dependency by writing the file
vcpkg.json
. - Install the dependencies with Vcpkg.
- Modify CMake to link the
GTest
target. - Build the project.
Unsurprisingly, we assume you have installed Vcpkg. If not, you can follow the official getting started to install Vcpkg. In addition, you might want to make sure that you can invoke the executable vcpkg
from your terminal. For example, I had to add the following line below to my .bashrc
.
alias vcpkg="/home/matheus/vcpkg/vcpkg"
In my case, /home/matheus/vcpkg
is the installation directory for vcpkg
.
Declaring The Google Test Dependency With Vcpkg
Similar to Conan’s conanfile.txt
, we declare our third-party dependencies inside the vcpkg.json
file. Unlike Conan, Vcpkg handles the dependency versions a little differently, so vcpkg.json
is a little more cumbersome, as you can see below.
{
"name": "multiply-tests",
"version": "0.0.0",
"builtin-baseline": "43235cf746cddb4981a8ef4abdb80aafe4d4e0e2",
"dependencies": [
{
"name": "gtest",
"version>=": "1.11.0"
}
]
}
As you can see, vcpkg.json
has a little more information than the Conan counterpart. More specifically, it follows a certain JSON schema that accepts certain options. You can find out more about it in the Vcpkg manifest documentation.
For the sake of clearing things up, I explain some of the information in the file vcpkg.json
below.
name
is the name of your package/project.version
defines the version of your package. Since we’re not exporting this project, you can add any semantic version here.builtin-baseline
is a commit into the Vcpkg repository. This represents the baseline “snapshot” of Vcpkg’s packages, meaning that the versions of the dependencies installed will be at least what is available in this commit.dependencies
should declare an array of third-party dependencies your project needs. Each dependency is a JSON object withname
and, optionally, a least version.
In our case, we only have one dependency: Google Tests version 1.11.0! For more information on packages that are available for Vcpkg, check out the packages browser for Vcpkg. Understandably, versioning with Vcpkg is very confusing, so I’d recommend checking out this Vcpkg versioning article by Microsoft.
Installing The Google Test Dependency With Vcpkg
Now that we declared the dependency on Google Tests, we need to ask Vcpkg to pull the code and build it locally, so we can include it in our project. Luckily for you, it’s a very simple command, as you can see in the box below.
# need to run this from the root dir
vcpkg install --x-install-root="build"
Unsurprisingly, the command above will use Vcpkg to download and build Google Test, and the installation files will be located under the build/vcpkg
and build/x64-linux
directories. Unlike Conan, Vcpkg creates “config” files that are invoked when you use find_package(...)
, so it’s basically a different mode of the same CMake command. I’d recommend looking at the CMake documentation if you’re interested in finding out how it works.
Modifying The CMake And Building The Google Tests
Assuming that the Vcpkg installation went smoothly, you should be able to just modify the tests CMakeLists.txt
and build the project. In fact, we end up with the same tests/CMakeLists.txt
as our Conan counterpart, both Conan and Vcpkg use the find_package(...)
mechanism.
find_package(GTest REQUIRED)
add_executable(multiply_test multiply_test.cpp)
target_link_libraries(multiply_test
PRIVATE
GTest::GTest
multiply)
add_test(multiply_gtests multiply_test)
Finally, the only thing remaining is configuring the project and building with CMake. The code box below shows the remaining steps.
# Run from project root
cd build
cmake .. \
-DCMAKE_TOOLCHAIN_FILE="Path-To/vcpkg.cmake"
cmake --build .
Essentially, the only unusual parameter we pass to CMake is -DCMAKE_TOOLCHAIN_FILE="PATH-TO/vcpkg.cmake"
. This value points to the integration toolchain file installed with Vcpkg. Usually, this is located at PATH-TO-VCPKG-INSTALL/scripts/buildsystems/vcpkg.cmake
. In case you cannot find this file, I recommend reading the Vcpkg guide on installing packages, as they describe where you can expect this file.
After this, you should have everything built under the build
directory. Moreover, you can run all the tests by invoking ctest
from the build
directory.
Get The Full Code Examples
If you struggled to get the full picture in this post or cannot yet manually write and build all the code in this post, feel free to clone/download the full example in the Github repository.
Once you clone the repository, there are three directories:
conan
is where you can find the Conan-based examples.fetch-content
has all the CMake-only project.vcpkg
contains everything you need to build the examples with Vcpkg.
In addition, each of the directories above has a build script you can run from the project’s root. For example, if you want to build the Conan example, use cd conan
, followed by ./scripts/build.sh
, and all the build files will be located under conan/build
.
Which Method Should You Choose To Integrate Google Test Into CMake?
In this post, we looked at three different ways to include GTest into your CMake project. On one hand, we had a “complete” CMake method with FetchContent
. On the other hand, we looked at including Google Tests with package managers.
If your project is very simple, and you have no more third-party dependencies aside from GoogleTest, then the FetchContent
way is perfectly acceptable. With this purely CMake method, you don’t need to install further tools to integrate GTest.
However, beware that FetchContent
only works well for other CMake dependencies, and it’s a lot harder to integrate non-CMake packages with it. In addition, the pulled dependencies are essentially copied and pasted into your build! Meaning that you may encounter target name clashes or other interferences with CMake.
If you have many dependencies and need to worry about versioning, you should probably choose a package manager for C++. As you’ve probably guessed, Conan and Vcpkg are the most popular package managers for C++. If you’re interested in learning more, check out my post on the differences between Conan and Vcpkg.
Have I missed anything? Do you have any questions/feedback regarding the post? Noticed any typos? Feel free to comment below and I’ll reply as soon as I can!
Be First to Comment