-
Book Overview & Buying
-
Table Of Contents
-
Feedback & Rating
Advanced C++ [Instructor Edition]
By :
Unit tests are an important part of programming in general. Basically, unit tests are little programs that use our classes in various scenarios with expected results, live in a parallel file hierarchy in our project, and do not end up in the actual executable but are executed separately by us during development to ensure that our code is behaving in expected ways. We should write unit tests for our C++ programs to ensure that they behave as they are supposed to after each change.
There are several C++ test frameworks that we can use with CMake. We will use Google Test, which has several benefits over other options. In the next exercise, we will prepare our project for unit testing with Google Test.
We have installed Google Test but our project is not set up to use Google Test for unit testing. In addition to the installation, there are settings that need to be carried out in our CMake project to have Google Test unit tests. Follow these steps to implement this exercise:
find_package(GTest)
if(GTEST_FOUND)
set(Gtest_FOUND TRUE)
endif()
if(GTest_FOUND)
include(GoogleTest)
endif()
# add these two lines below
enable_testing()
add_subdirectory(tests)
This is all we need to add to our main CMakeLists.txt file.
include(GoogleTest)
add_executable(tests CanTest.cpp)
target_link_libraries(tests GTest::GTest)
gtest_discover_tests(tests)
Let's dissect this code line by line. The first line brings in the Google Test capability. The second line creates the tests executable, which will include all our test source files. In this case, we only have one CanTest.cpp file, which will just verify that the testing works. After that, we link the GTest library to the tests executable. The last line identifies all individual tests in the tests executable and adds them to CMake as a test. This way, various test tools will be able to tell us which individual tests failed and which ones passed.
#include "gtest/gtest.h"
namespace {
class CanTest: public ::testing::Test {};
TEST_F(CanTest, CanReallyTest) {
EXPECT_EQ(0, 0);
}
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
The TEST_F line is an individual test. Now, EXPECT_EQ(0, 0) is testing whether zero is equal to zero, which will always succeed if we can actually run the test. We will later add the results of our own classes here to be tested against various values. Now we have the necessary setup for Google Test in our project. Next, we will build and run these tests.
Now, we will discuss how to build, run, and write unit tests. The example that we have so far is a simple dummy test that is ready to be built and run. Later, we will add tests that make more sense and view the output of passing and failing tests. In the following exercise, we will build, run, and write unit tests for the project that we created in the previous exercise.
So far, you have created a project with GoogleTest set up, but you did not build or run the tests we created. In this exercise, we will build and run the tests that we created. Since we added our tests folder using add_subdirectory, building the project will automatically build the tests. Running the tests will require some more effort. Perform the following steps to complete the exercise:



As you can see, our project now has two executable targets. They both live in the build folder, as with any other build artifact. Their locations are build/Debug/CxxTemplate and build/Debug/tests/tests. Since they are executables, we can simply run them.
./build/Debug/tests/tests
The preceding code generates the following output in the terminal:

This is the simple output of our tests executable. If you want to see whether the tests have passed, you can simply run this. However, testing is so much more than that.
cd build/Debug/tests
ctest
cd ../../..
And here is the output that you will see:

The ctest command can run your tests executable with a number of options, including the ability to submit test results automatically to online dashboards. Here, we will simply run the ctest command; its further features are left as an exercise for the interested reader. You can type ctest --help or visit the online documentation to discover ctest further at https://cmake.org/cmake/help/latest/manual/ctest.1.html#.




The result will be similar to the following screenshot:
This is a nice report that contains entries for all tests—only one for now. You may prefer this if you do not want to leave the IDE. Furthermore, when you have many tests, this interface can help you filter them effectively. Now you have built and run tests that were written using Google Test. You ran them in a couple of different ways, including directly executing the test, using ctest, and using Eclipse CDT. In the next section, we will solve an exercise wherein we will actually test the functionality of our code.
You have run simple tests but now you want to write meaningful tests that are testing functionality. In the initial activity, we created SumFunc.cpp, which had the sum function. Now, in this exercise, we will write a test for that file. In this test, we will use the sum function to add two numbers and verify that the result is correct. Let's recall the contents of the following files with the sum function from before:
#ifndef SRC_SUMFUNC_H_
#define SRC_SUMFUNC_H_
int sum(int a, int b);
#endif /* SRC_SUMFUNC_H_ */
#include "SumFunc.h"
#include <iostream>
int sum(int a, int b) {
return a + b;
}
add_executable(CxxTemplate
src/CxxTemplate.cpp
src/ANewClass.cpp
src/SumFunc.cpp
)
Also, let's recall our CantTest.cpp file, which has the main() function of our unit tests:
#include "gtest/gtest.h"
namespace {
class CanTest: public ::testing::Test {};
TEST_F(CanTest, CanReallyTest) {
EXPECT_EQ(0, 0);
}
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Perform the following steps to complete the exercise:
#include "gtest/gtest.h"
#include "../src/SumFunc.h"
namespace {
class SumFuncTest: public ::testing::Test {};
TEST_F(SumFuncTest, CanSumCorrectly) {
EXPECT_EQ(7, sum(3, 4));
}
}
Note that this does not have a main() function since CanTest.cpp has one and these will be linked together. Secondly, note that this includes SumFunc.h, which is in the src folder of the project and uses it as sum(3, 4) inside the test. This is how we use our project code in tests.
include(GoogleTest)
add_executable(tests CanTest.cpp SumFuncTest.cpp ../src/SumFunc.cpp) # added files here
target_link_libraries(tests GTest::GTest)
gtest_discover_tests(tests)
Note that we added both the test (SumFuncTest.cpp) and the code that it tests (../src/SumFunc.cpp) to the executable, as our test code is using the code from the actual project.

We can add such tests to our project and all of them will appear on the screen as shown in the preceeding screenshot.
TEST_F(SumFuncTest, CanSumCorrectly) {
EXPECT_EQ(7, sum(3, 4));
}
// add this test
TEST_F(SumFuncTest, CanSumAbsoluteValues) {
EXPECT_EQ(6, sum(3, -3));
}
Note that this test assumes that the absolute values of the inputs are summed up, which is incorrect. The result of this call is 0 but is expected to be 6 in this example. This is the only change that we have to make in our project to add this test.

As you can see in the preceding figure, the first two tests passed and the last test failed. When we see this output, there are two options: either our project code is wrong, or the test is wrong. In this case, our test is wrong. This is because our CanSumAbsoluteValues test case expects that 6 is equal to sum(3, -3). This is because we assumed that our function sums up the absolute values of the integers provided. However, this is not the case. Our function simply adds the given numbers, whether they are positive or negative. Therefore, this test had a faulty assumption and failed.
TEST_F(SumFuncTest, CanSumCorrectly) {
EXPECT_EQ(7, sum(3, 4));
}
// change this part
TEST_F(SumFuncTest, CanUseNegativeValues) {
EXPECT_EQ(0, sum(3, -3));
}
Finally, we have set up Google Test with CMake both in our system and project. We also wrote, built, and ran unit tests with Google Test, both in the terminal and in Eclipse. Ideally, you should write unit tests for every class and cover every possible usage. You should also run the tests after each major change and make sure you do not break existing code. In the next section, we will perform an activity of adding a new class and its test.
As you develop a C++ project, you add new source files to it as the project grows. You also write tests for them to ensure that they are working properly. In this activity, we will add a new class that simulates 1D linear motion. The class will have double fields for position and velocity. It will also have a advanceTimeBy() method, which receives a double dt parameter, which modifies position based on the value of velocity. Use EXPECT_DOUBLE_EQ instead of EXPECT_EQ for double values. In this activity, we will add a new class and its test to the project. Follow these steps to perform this activity:
The final test results should look similar to the following:
The solution for this activity can be found on page 622.
Adding new classes and their tests is a very common task in C++ development. We create classes for various reasons. Sometimes, we have a nice software design plan and we create the classes that it calls for. Other times, when a class becomes too large and monolithic, we separate some of its responsibility to another class in a meaningful way. Having this task be practical is important to prevent dragging your feet and ending up with huge monolithic classes. In the following section, we discuss what happens during the compilation and linking stages. This will give us a better perspective of what is happening under the hood of C++ programs.
Change the font size
Change margin width
Change background colour