Rosanswers logo

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
  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...

  1. 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.
  2. 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.
  3. 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!

Originally posted by alextoind on ROS Answers with karma: 217 on 2017-01-12

Post score: 6


1 Answer 1


Rosanswers logo

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).

This problem can be addressed by using dependency injection on the ROS node level. Assuming you have a single class which implements the hardware interfacing you could e.g. use a ROS parameter to indicate the replacement of the actual hardware interfacing calls in the ROS node construction (compare library level constructor injection) with a test double class (dummy, stub or fake). The test double must have the same production specific API that it is conform to the ROS node's calls. To configure the test double class (e.g. to continuously provide test specific fake sensor data) you have to add test specific class API calls to the ROS node as well e.g. using a test hook means compile time insertion of these additional "test" specific API calls. On the ROS node level the configuration of the test specific test double API calls can be implemented with ROS parameters as well.

EDIT: As soon as I find some time I want to add examples of how to actually doing this here https://github.com/fkromer/building-reliability-into-ros-packages/blob/master/design/design_for_testability.md#break-the-dependency-on-real-sensors

Originally posted by thinwybk with karma: 468 on 2017-08-01

This answer was ACCEPTED on the original site

Post score: 2

Original comments

Comment by gvdhoorn on 2017-10-25:
This answer is nice, but I would like it even more if it also mentioned that structuring a node in such a way that the node itself (ie: the ROS API it provides) is only a thin wrapper around a non-ROS library that implements the 'business logic' is a best practice, and has been for years ..

Comment by gvdhoorn on 2017-10-25:
.. (and is not even ROS specific).

Structuring things like that should make testing of such setups much more like 'regular' programs (if there is such a thing), which would help demystifying and unraveling the "idea" that testing ROS programs in this way is special.

Comment by gvdhoorn on 2017-10-25:
Note that testing the ROS API and node<->nodegraph interaction is relatively ROS specific, but that is something else, and could still benefit from the realisation that generic testing techniques for async, event-based and sync, rpc based systems apply to ROS contexts as well.

Comment by thinwybk on 2017-10-25:
I am planning to add an explanation and an example about "usual design considerations" like separating non-ROS specific (library) code from ROS specific code as well... whenever I find more time. (I already began the explanation part which I can push to github later...)

Comment by thinwybk on 2017-10-25:
BTW: @gvdhoorn would be great if you could review the content on github at some point in time...

Comment by gvdhoorn on 2017-10-25:
I'd be happy to.

I think we contacted you in the context of ROSIN, did we not? That would nicely tie this all together.

Comment by thinwybk on 2017-10-25:
Right. (I will replay w.r.t. scheduling of the interview with Adam about possible improvements of the ROS Q&A, etc. after work.)

Comment by aPonza on 2018-11-07:
@thinwybk would it be possible for you to fix the link to the repository? I can't find the specific examples you're mentioning (although this seems related) and I can only find outdated documentation or very unspecific processes.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.