Ideal C++ Node Architecture for Unit Testing
Hi,
I have been using a combination of gtest and rostest to validate (or verify?) my code. I was wondering if there was a better way to architect my code so that my unit tests do not unnecessarily require me to spool up a roscore. In other words, if I don't have to use rostest, I'd prefer to just use gtest.
For example, all of my C++ nodes follow the following the same basic structure structure:
.
+-- src
| +-- src/super_cool_stuff_node.cpp
| +-- src/super_cool_stuff.cpp
+-- includes
| +-- include/supper_cool_stuff.h
Where src/super_cool_stuff_node.cpp doesn't contain any real logic. I just declare the node handle, instantiate my custom class, and set the execution rate. In fact, I use this template for just about all C++ nodes I write.
#include "myPackage/supper_cool_stuff.h"
int main(int argc, char **argv)
{
ros::init(argc, argv, "super_cool_node"); //node name
ros::NodeHandle nh; // create a node handle;
//Instantiate the class
MyClass my_class(&nh);
// Tell ROS how fast to run this node.
ros::Rate loop_rate(10);
while (ros::ok())
{
ros::spinOnce();
my_class.doStuff();
loop_rate.sleep();
}
return 0;
}
I then define and declare the class in src/super_cool_stuff.cpp and include/supper_cool_stuff.h, respectively.
#include "myPackage/supper_cool_stuff.h"
#define pi 3.14159265358979323846
MyClass::MyClass(ros::NodeHandle* nodehandle) :
nh_(*nodehandle)
{ // constructor
ROS_INFO("Beginning of Class Constructor");
node_name_ = ros::this_node::getName();
//Define Static Calibrations
nh_.param<std::string>("robot_name", robot_name_, "no_name");
//Initialize Variables
finished_placing_waypoints_ = false;
//Define Subscribers
odom_sub_ = nh_.subscribe(odom_topic_source_, 1, &MyClass::odomMsgCallback, this);
//Define Publishers
autonomy_commands_pub_ = nh_.advertise<geometry_msgs::TwistStamped>(node_name_ + "/cmd_vel", 10);
//Dynamic Reconfigure
dynamic_reconfigure::Server<MyClass::pfConfig>::CallbackType f; // Start up dynamic reconfigure server
f = boost::bind(&MyClass::dynamicReconfigCallback, this, _1, _2);
server_.setCallback(f);
ROS_INFO("End of Class Constructor");
}
// Class Methods
float MyClass::doSomethingSpecific(float speed, bool status)
{
}
and finally the header file for the class:
#ifndef MY_CLASS_H
#define MY_CLASS_H
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
class MyClass
{
public:
MyClass(ros::NodeHandle* nodehandle);
ros::NodeHandle nh_;
bool finished_placing_waypoints_;
ros::Subscriber odom_sub_;
ros::Publisher autonomy_commands_pub_;
dynamic_reconfigure::Server<path_follower::pfConfig> server_;
float doSomethingSpecific(float speed, bool status);
}
The problem that I have is that if I want to unit test the class method doSomethingSpecific() using gtest, I have to instantiate the class MyClass. The class MyClass requires a node handle to be passed into it. I can't define a node handle without running roscore. I understand a node handle is required for subscribers, publishers, parameters, etc., but for the class methods that truly don't need a node handle, like doSomethingSpecific(), is there a better way to architect the node so that these functions can be tested without running roscore? Should these class methods be extracted into a second class? Should I move all of the logic that requires a node handle out of the class file src/super_cool_stuff.cpp and into src/super_cool_stuff_node.cpp? I'm trying to figure out the best way to architect the code for test driven development.
Thanks for your help.