ROS Resources: Documentation | Support | Discussion Forum | Index | Service Status | ros @ Robotics Stack Exchange
Ask Your Question
6

Structure a ROS node for easily testing

asked 2017-01-12 03:13:46 -0500

alextoind gravatar image

updated 2017-01-13 10:44:36 -0500

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

  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!

edit retag flag offensive close merge delete

1 Answer

Sort by ยป oldest newest most voted
2

answered 2017-08-01 08:18:25 -0500

thinwybk gravatar image

updated 2017-10-25 03:02:02 -0500

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

edit flag offensive delete link more

Comments

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

gvdhoorn gravatar image gvdhoorn  ( 2017-10-25 03:28:16 -0500 )edit

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

gvdhoorn gravatar image gvdhoorn  ( 2017-10-25 03:29:52 -0500 )edit

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.

gvdhoorn gravatar image gvdhoorn  ( 2017-10-25 03:32:40 -0500 )edit

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

thinwybk gravatar image thinwybk  ( 2017-10-25 03:43:58 -0500 )edit

BTW: @gvdhoorn would be great if you could review the content on github at some point in time...

thinwybk gravatar image thinwybk  ( 2017-10-25 03:45:47 -0500 )edit

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.

gvdhoorn gravatar image gvdhoorn  ( 2017-10-25 03:48:39 -0500 )edit

Right. (I will replay w.r.t. scheduling of the interview with Adam about possible improvements of the ROS Q&A, etc. after work.)

thinwybk gravatar image thinwybk  ( 2017-10-25 03:53:27 -0500 )edit

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

aPonza gravatar image aPonza  ( 2018-11-07 10:58:16 -0500 )edit

Question Tools

4 followers

Stats

Asked: 2017-01-12 03:13:46 -0500

Seen: 1,463 times

Last updated: Oct 25 '17