Structure a ROS node for easily testing
Since I've started using ROS few years ago, I used to simplify my life with basic templates like the following:
int main(int argc, char** argv) {
ros::init(argc, argv, "my_node");
my_namespace::MyNode my_node; // includes a ros::AsyncSpinner
ros::waitForShutdown();
return 0;
}
where everything is started in the MyNode()
constructor, for example by retrieving custom configurations from the Parameter Server.
Now, that I've started to gamble on the Google Testing/Mocking Framework for my projects, things are getting complicated.
I know that I have to test first the core libraries using only gtest
in a ROS agnostic way and then the nodes behaviour and interoperation using both gtest
and rostest
. Nonetheless, if MyNode
has several public non-static methods, I must initialize a class object to access them from the TEST
macros, and this become ROS dependent. Moreover, if my node is aimed to control hardware, I usually initialize the communication in the constructor to avoid to start it manually each time. This can be also a problem because it forces me to stay with the device connected while testing (which is ok for higher level testing, but not for unit testing).
What is the best way to strucutre a ROS node to easily test it properly? I could set a new public method start()
with the major things of the constructor (e.g. device communication setup) and call il from within the main, but I'm not very convinced about it... Should I extract MyNodeImpl
from MyNode
and test that one?
EDIT: further thoughts...
- I was wrong with the PIMPL approach because even if it separates the code in a way that I could like for my purpose (and which has also other known advantages), it also hides the code that I'd like to test. I know that you should test only the public interface, yes, but it's not always the case.
- On the other hand, if I separate the hardware setup and all those stuff in another public method called from within the main, I do not resolve the ROS dependency of the unit test, i.e. if you have a
ros::NodeHandle
as a member of the class you need to initialize ROS first. - A very-ugly-but-quick workaround could be the one to add a specific fake protected constructor of the class and use it only for testing. This could be a simple empty constructor with a specific parametrization, obviously different from the others. A part from the ugliness, the real drawback is that this method is accessible by derived classes and yet, it is not ROS agnostic.
I'm a bit frustrated... I mean, testing should help, not be the trouble. Anyway, if you have any suggestion I'm all ears!