This is an example package for the usage of Google Tests and Google Mocks (gtest
and gmock
) with ROS.
Test cases are executed in the root directory of the workspace with:
catkin_make run_tests
Files containing the test cases should be located at <package_root>/test
.
A file executing the test cases is required.
Within this file a ros node and a ros nodehandle can be initialized if these are required for the test cases.
If a rops node and a ros nodehandle are created a roscore is required.
int main(int argc, char **argv){
testing::InitGoogleTest(&argc, argv);
ros::init(argc, argv, "tester");
ros::NodeHandle nodehandle;
// execute all tests defined in CMakeLists.txt
return RUN_ALL_TESTS();
}
The specific test cases can be placed in the same file as the main or can be placed in different files.
All files containing test cases shall be located in <package_root>/test
.
Tests are defined in with macros. A name for the test suite (CamelCaseStartingUpper) and the test case (camelCaseStartingLower) are required. The test utilizes assertions (assert) and expectations (expect) see here. Note that the difference between assert and expect is that after a failed assert the test stops and the test fails immediately, after a failed expect the test will continue and will fail after the test finishes.
TEST(TestSuite, testCase) {
EXPECT_TRUE(true);
}
Test can use mocks to ease testing. Mocks are designed to mock the functionality of virtual functions. Nevertheless it is possible to mock the functionality of non-virtual functions.
A test including a mock can verify that the mock was called as expected.
A class with a virtual function could look like this:
namespace virtually {
class Publisher : public ros::Publisher {
private:
ros::Publisher* publisher_pointer;
public:
void setPublisher(ros::Publisher*);
virtual void publish(const std_msgs::String&) const;
};
}
This is a wrapper class for the ros publisher shadowing the publish function with a virtual function.
A mock for this class could look like this:
namespace virtually {
class MockPublisher : public ros::Publisher {
public:
MOCK_METHOD(void, publish, (std_msgs::String&), (const, override));
};
}
Notice that the mock does not have to overwrite all functions.
A test for such a mock could look like this:
TEST(VirtualTalkerTest, verifyPublisherCall) {
auto mockPublisher = std::make_shared<virtually::MockPublisher>();
auto talker = std::make_shared<virtually::Talker>();
talker->setPublisher(mockPublisher.get());
EXPECT_CALL(*mockPublisher.get(), publish(testing::_)).Times(1);
std_msgs::String message;
talker->publish(message);
}
Notice that the arguments for the call are specified with testing::_
which matched any call.
The publisher as defined in ros has no virtual functions.
To verify calls on the publisher the class using the publisher can be a templated class. A mock class of the publisher without inheritance (the signature of the mocked function needs to be the same) is required. The mock class can be passed to the templated class.
The templated class could look like this:
namespace non_virtually { ;
template<typename T>
class Talker {
private:
T *publisher_pointer;
public:
void setPublisher(T *publisher) {
this->publisher_pointer = publisher;
};
virtual void publish(const std_msgs::String &message) const {
ROS_DEBUG("non-virtual publish function is invoked");
this->publisher_pointer->publish(message);
};
};
}
A mock for such a class could look like this:
namespace non_virtually {
class MockPublisher {
public:
MOCK_METHOD(void, publish, (const std_msgs::String&), (const));
};
}
A test for such a mock could look like this:
TEST(NonVirtualTalkerTest, verifyPublisherCall) {
auto mockPublisher = std::make_shared<non_virtually::MockPublisher>();
auto talker = std::make_shared<non_virtually::Talker<non_virtually::MockPublisher>>();
talker->setPublisher(mockPublisher.get());
EXPECT_CALL(*mockPublisher.get(), publish(testing::_)).Times(1);
std_msgs::String message;
talker->publish(message);
}
A more detailed documentation for mocks is provided here.
The CMakeLists.txt
needs to be extended:
catkin_add_gtest(target
source_1
...
source_n
)
target_link_libraries(target
library_1
...
library_n
gmock
)
The package.xml
needs to be extended:
<test_depend>rosunit</test_depend>
The following packages are required for gtest
and gmock
:
libgtest-dev
(installation in ubuntu:sudo apt-get install libgtest-dev
)libgmock-dev
(installation in ubuntu:sudo apt-get install libgmock-dev
)