Google Test (a.k.a GTest) is an open source unit testing structure. From the official website
Test Suite vs Test Case: A test suite contains one or many tests. You should group your tests into test suites that reflect the structure of the tested code. When multiple tests in a test suite need to share common objects and subroutines, you can put them into a test fixture class.
Some common macros include:
EXPECT_EQ vs ASSERT_EQ: EXPECT_* versions generate nonfatal failures, which don’t abort the current function. Usually EXPECT_* are preferred, as they allow more than one failure to be reported in a test. However, you should use ASSERT_* if it doesn’t make sense to continue when the assertion in question fails.
Usage
Install GTest if your environment doesn’t have it
1
2
3
4
5
6
git clone https://github.com/google/googletest
cd googletest
mkdir-p build install
cd build
cmake -DCMAKE_INSTALL_PREFIX=../install .. Install locally
make -j32install
Simple Test Case
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Test functiondoublegetFrobeniusNorm(constcv::Mat&m){// element-wise matrix multiplication // cv::sum sums of elements across all dimensionsreturnstd::sqrt(cv::sum(m.mul(m))[0]);}// Test case#include<gtest/gtest.h>TEST(CvUtilsTest,TestFrobeniusNorm){cv::Mat_<double>m(3,3);m<<1.0,2.0,3.0,0.0,0.0,0.0,1.0,2.0,0.0;doublenorm=getFrobeniusNorm(m);EXPECT_EQ(norm,std::sqrt(1+4+9+1+4));}
Test Fixture
Test fixture is a mechanism to share code between test cases in a test suite. One common misconception is “the text fixture will be reused across tests”. THAT IS NOT TRUE! Test fixtures are only for sharing initialization code. Below, we have an example
classMyTestFixture:public::testing::Test{protected:voidSetUp()override{str_="Hello World";std::cout<<str_<<std::endl;}voidTearDown()override{// For extra destruction work}staticvoidSetUpTestSuite(){// Initialize shared resource (like database access) for the entire test suite}staticvoidTearDownTestSuite(){// Destruct test suite's shared resources}std::stringstr_;};TEST_F(MyTestFixture,SomeTest){}
Note, to use a test fixture, one needs TEST_F instead of TEST.
If a fatal failure could happen, ASSERT_* is a good choice for that purpose, but it cannot be used in ctor or dtor and can be only used in SetUp()
SetUp() can catch exceptions. Arguably, it could be a good idea if you don’t want uncaught exceptions to interrupt your normal tests (e.g., an emergency test fix)
Test Environment
Test environment helps initialize test resource that’s shared for the entire test binary, e.g., logging system initialization. One potential bug is this environment could be shared across multiple compilation units, if they comprise the same binary.
1
2
3
4
5
6
7
8
9
10
11
12
classEnvironment:public::testing::Environment{public:~Environment()override{}voidSetUp()override{std::cout<<"This is intialized only once globally"<<std::endl;}voidTearDown()override{std::cout<<"This is destructed only once globally"<<std::endl;}};intmain(intargc,char**argv){testing::AddGlobalTestEnvironment(newEnvironment);testing::InitGoogleTest(&argc,argv);returnRUN_ALL_TESTS();}
General Gtest with CMake
If using CMakeLists.txt, we can create a test executable for each of them:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Test filesfile(GLOB_RECURSE TEST_FILES "tests/unit/*cpp")foreach(TEST_FILE ${TEST_FILES})# NAME_WE means "name without extension"get_filename_component(TEST_EXEC_NAME ${TEST_FILE} NAME_WE)add_executable(${TEST_EXEC_NAME}_test ${TEST_FILE})add_test(NAME ${TEST_EXEC_NAME}_test COMMAND ${TEST_EXEC_NAME}_test)target_link_libraries(${TEST_EXEC_NAME}_test
${PROJECT_NAME}_dependencies
gtest
gtest_main
Threads::Threads
)endforeach()enable_testing()
add_test() does NOT create the test executable, it simply registers the exectuble as a test.
Threads::Threads must be added to the above gtest. Otherwise, the gtest will fail silently, which is really annoying!!)
Gtest For ROS
The Snippet in section “General Gtest with CMake” already can be built by catkin:
1
2
3
4
5
6
# build all executables
catkin build
# Run tests
catkin test
# Or for a specific package
catkin test <PACKAGE>
The test executable is in devel/lib/${PACKAGE_NAME}
Upon failures, catkin test could throw a failure “double freeing” if we do not have a main() in our tests and uses gtest_main. This wouldn’t arise if we run the executable alone.
So, my best guess is this is a memory-freeing bug in catkin test with gtest_main. But if we add a main ourselves, and uses catkin_add_gtest(), we should be all good.
catkin testrequires roscore to be running. If it’s not run, it will show as stuck in the building phase.
The CMakeLists.txt is as below:
1
2
3
4
5
6
7
8
9
10
file(GLOB_RECURSE TEST_FILES "tests/unit/*cpp")foreach(TEST_FILE ${TEST_FILES})# NAME_WE means "name without extension"get_filename_component(TEST_EXEC_NAME ${TEST_FILE} NAME_WE)catkin_add_gtest(${TEST_EXEC_NAME}_test ${TEST_FILE})target_link_libraries(${TEST_EXEC_NAME}_test
${catkin_LIBRARIES})endforeach()enable_testing()